@devtron-labs/devtron-fe-common-lib 0.0.56 → 0.0.58-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/.eslintignore +96 -0
- package/.eslintrc.js +84 -16
- package/.github/pull_request_template.md +25 -0
- package/.github/workflows/ci.yml +31 -0
- package/.github/workflows/release-package.yml +2 -3
- package/.husky/pre-commit +5 -0
- package/.lintstagedrc +3 -0
- package/.nvmrc +1 -0
- package/dist/Common/Common.service.d.ts +2 -2
- package/dist/Common/Constants.d.ts +0 -8
- package/dist/Common/CustomInput/CustomInput.d.ts +1 -1
- package/dist/Common/CustomInput/Types.d.ts +0 -1
- package/dist/Common/DebouncedSearch/DebouncedSearch.d.ts +2 -0
- package/dist/Common/EmptyState/GenericEmptyState.d.ts +1 -1
- package/dist/Common/Helper.d.ts +8 -1
- package/dist/Common/Pagination/PageSizeSelector.d.ts +4 -0
- package/dist/Common/Pagination/Pagination.d.ts +5 -0
- package/dist/Common/Pagination/constants.d.ts +5 -0
- package/dist/Common/Pagination/index.d.ts +1 -0
- package/dist/Common/Pagination/types.d.ts +51 -0
- package/dist/Common/Pagination/utils.d.ts +6 -0
- package/dist/Common/SearchBar/SearchBar.component.d.ts +39 -0
- package/dist/Common/SearchBar/index.d.ts +2 -0
- package/dist/Common/SearchBar/types.d.ts +35 -0
- package/dist/Common/Types.d.ts +15 -3
- package/dist/Common/index.d.ts +2 -0
- package/dist/index.js +462 -229
- package/package.json +19 -2
- package/src/Assets/Icon/ic-search.svg +6 -0
- package/src/Common/Constants.ts +0 -9
- package/src/Common/CustomInput/CustomInput.tsx +2 -3
- package/src/Common/CustomInput/Types.ts +0 -1
- package/src/Common/DebouncedSearch/DebouncedSearch.tsx +2 -0
- package/src/Common/EmptyState/GenericEmptyState.tsx +13 -45
- package/src/Common/Helper.tsx +45 -16
- package/src/Common/Pagination/PageSizeSelector.tsx +78 -0
- package/src/Common/Pagination/Pagination.tsx +127 -0
- package/src/Common/Pagination/constants.ts +6 -0
- package/src/Common/Pagination/index.tsx +3 -0
- package/src/Common/Pagination/pagination.scss +123 -0
- package/src/Common/Pagination/types.ts +55 -0
- package/src/Common/Pagination/utils.tsx +24 -0
- package/src/Common/SearchBar/SearchBar.component.tsx +120 -0
- package/src/Common/SearchBar/index.ts +2 -0
- package/src/Common/SearchBar/searchBar.scss +31 -0
- package/src/Common/SearchBar/types.ts +36 -0
- package/src/Common/Types.ts +15 -3
- package/src/Common/index.ts +2 -0
package/package.json
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@devtron-labs/devtron-fe-common-lib",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.58-beta-3",
|
|
4
4
|
"description": "Supporting common component library",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"scripts": {
|
|
7
|
+
"prepare": "node -e \"try { require('husky').install() } catch (e) {}\"",
|
|
8
|
+
"lint": "tsc --noEmit && eslint 'src/**/*.{js,jsx,ts,tsx}' --max-warnings 0",
|
|
9
|
+
"lint-fix": "eslint 'src/**/*.{js,jsx,ts,tsx}' --fix",
|
|
7
10
|
"test": "exit 0",
|
|
8
11
|
"build-lib": "rollup -c --bundleConfigAsCjs",
|
|
9
|
-
"build-watch": "rollup -c --bundleConfigAsCjs -w"
|
|
12
|
+
"build-watch": "rollup -c --bundleConfigAsCjs -w",
|
|
13
|
+
"lint-staged": "lint-staged"
|
|
10
14
|
},
|
|
11
15
|
"repository": {
|
|
12
16
|
"type": "git",
|
|
@@ -30,7 +34,20 @@
|
|
|
30
34
|
"@types/react": "17.0.39",
|
|
31
35
|
"@types/react-dom": "17.0.13",
|
|
32
36
|
"@types/react-router-dom": "^5.3.3",
|
|
37
|
+
"@typescript-eslint/eslint-plugin": "^6.17.0",
|
|
38
|
+
"@typescript-eslint/parser": "^6.17.0",
|
|
39
|
+
"eslint": "^8.56.0",
|
|
40
|
+
"eslint-config-airbnb": "^19.0.4",
|
|
41
|
+
"eslint-config-prettier": "^9.1.0",
|
|
42
|
+
"eslint-plugin-import": "^2.29.1",
|
|
43
|
+
"eslint-plugin-jsx-a11y": "^6.8.0",
|
|
44
|
+
"eslint-plugin-prettier": "^5.1.2",
|
|
45
|
+
"eslint-plugin-react": "^7.33.2",
|
|
46
|
+
"eslint-plugin-react-hooks": "^4.6.0",
|
|
47
|
+
"husky": "^7.0.4",
|
|
48
|
+
"lint-staged": "^12.5.0",
|
|
33
49
|
"moment": "^2.29.4",
|
|
50
|
+
"prettier": "^3.1.1",
|
|
34
51
|
"react-select": "5.2.2",
|
|
35
52
|
"react-toastify": "^8.2.0",
|
|
36
53
|
"rollup": "^3.10.0",
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
|
2
|
+
<g fill="none" fill-rule="evenodd">
|
|
3
|
+
<path d="M0 0h24v24H0z"/>
|
|
4
|
+
<path fill="#959BA1" d="M10 2a8 8 0 0 1 6.32 12.905l5.044 5.045a1 1 0 1 1-1.414 1.414l-5.045-5.043A8 8 0 1 1 10 2zm0 2a6 6 0 1 0 0 12 6 6 0 0 0 0-12z"/>
|
|
5
|
+
</g>
|
|
6
|
+
</svg>
|
package/src/Common/Constants.ts
CHANGED
|
@@ -242,13 +242,4 @@ export const RepositoryAction = {
|
|
|
242
242
|
CHART_PUSH: "CHART_PUSH"
|
|
243
243
|
}
|
|
244
244
|
|
|
245
|
-
export enum APPROVAL_MODAL_TYPE {
|
|
246
|
-
CONFIG = 'CONFIG',
|
|
247
|
-
DEPLOYMENT = 'DEPLOYMENT',
|
|
248
|
-
}
|
|
249
245
|
export const MAX_Z_INDEX = 2147483647
|
|
250
|
-
|
|
251
|
-
export const SELECTED_APPROVAL_TAB_STATE = {
|
|
252
|
-
APPROVAL: 'approval',
|
|
253
|
-
PENDING: 'pending',
|
|
254
|
-
}
|
|
@@ -28,8 +28,7 @@ export function CustomInput({
|
|
|
28
28
|
defaultValue,
|
|
29
29
|
onKeyDown,
|
|
30
30
|
required,
|
|
31
|
-
additionalErrorInfo
|
|
32
|
-
inputWrapClassName = '',
|
|
31
|
+
additionalErrorInfo
|
|
33
32
|
}: CustomInputProps) {
|
|
34
33
|
|
|
35
34
|
function handleError(error: any): any[] {
|
|
@@ -93,7 +92,7 @@ export function CustomInput({
|
|
|
93
92
|
}
|
|
94
93
|
|
|
95
94
|
return (
|
|
96
|
-
<div className=
|
|
95
|
+
<div className="flex column left top">
|
|
97
96
|
{renderInputLabel()}
|
|
98
97
|
<input
|
|
99
98
|
data-testid={dataTestid}
|
|
@@ -4,6 +4,8 @@ import { ReactComponent as ICClear } from '../../Assets/Icon/ic-error-cross.svg'
|
|
|
4
4
|
import { DebouncedSearchProps } from './Types'
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
+
* @deprecated Use `SearchBar` component instead.
|
|
8
|
+
*
|
|
7
9
|
* @param onSearch - Callback function to be called on search
|
|
8
10
|
* @param Icon - (Optional) Icon to be shown before the input
|
|
9
11
|
* @param iconClass - (Optional) Class for the icon
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React from 'react'
|
|
1
|
+
import React from 'react';
|
|
2
2
|
import AppNotDeployed from '../../Assets/Img/app-not-deployed.png'
|
|
3
3
|
import { GenericEmptyStateType, ImageType } from '../Types'
|
|
4
4
|
|
|
@@ -19,56 +19,24 @@ function GenericEmptyState({
|
|
|
19
19
|
SvgImage,
|
|
20
20
|
noImage,
|
|
21
21
|
layout = 'column',
|
|
22
|
-
contentClassName = ''
|
|
23
|
-
imageStyles = {},
|
|
22
|
+
contentClassName = ''
|
|
24
23
|
}: GenericEmptyStateType): JSX.Element {
|
|
25
|
-
const isRowLayout = layout === 'row'
|
|
26
|
-
|
|
27
|
-
const getImageSize = () => {
|
|
28
|
-
switch (imageType) {
|
|
29
|
-
case ImageType.Large:
|
|
30
|
-
return { width: '250px', height: '200px' }
|
|
31
|
-
default:
|
|
32
|
-
return { width: '200px', height: '160px' }
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
24
|
+
const isRowLayout = layout === 'row';
|
|
25
|
+
|
|
36
26
|
return (
|
|
37
27
|
<div
|
|
38
|
-
className={`flex ${isRowLayout ? 'dc__gap-32' : 'column dc__gap-20'} empty-state ${
|
|
39
|
-
classname ? classname : ''
|
|
40
|
-
}`}
|
|
28
|
+
className={`flex ${isRowLayout ? 'dc__gap-32' : 'column dc__gap-20'} empty-state ${classname ? classname : ''}`}
|
|
41
29
|
style={styles}
|
|
42
30
|
data-testid="generic-empty-state"
|
|
43
31
|
{...(heightToDeduct >= 0 && { style: { ...styles, height: `calc(100vh - ${heightToDeduct}px)` } })}
|
|
44
|
-
>
|
|
45
|
-
{!SvgImage ?
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
width: `${getImageSize().width}`,
|
|
53
|
-
height: `${getImageSize().height}`,
|
|
54
|
-
}}
|
|
55
|
-
alt="empty-state"
|
|
56
|
-
/>
|
|
57
|
-
)
|
|
58
|
-
) : (
|
|
59
|
-
<SvgImage
|
|
60
|
-
style={{
|
|
61
|
-
...imageStyles,
|
|
62
|
-
width: `${getImageSize().width}`,
|
|
63
|
-
height: `${getImageSize().height}`,
|
|
64
|
-
}}
|
|
65
|
-
/>
|
|
66
|
-
)}
|
|
67
|
-
<div
|
|
68
|
-
className={`flex column dc__gap-10 dc__mxw-300 ${
|
|
69
|
-
isRowLayout ? 'dc__align-start' : ''
|
|
70
|
-
} ${contentClassName}`}
|
|
71
|
-
>
|
|
32
|
+
>
|
|
33
|
+
{!SvgImage ? !noImage && <img className={imageClassName ? imageClassName : ''}
|
|
34
|
+
src={image || AppNotDeployed}
|
|
35
|
+
width={imageType === ImageType.Medium ? '200' : '250'}
|
|
36
|
+
height={imageType === ImageType.Medium ? '160' : '200'}
|
|
37
|
+
alt="empty-state"
|
|
38
|
+
/> : <SvgImage />}
|
|
39
|
+
<div className={`flex column dc__gap-10 dc__mxw-300 ${isRowLayout ? 'dc__align-start' : ''} ${contentClassName}`}>
|
|
72
40
|
<h4 className="title fw-6 cn-9 mt-0 mb-0">{title}</h4>
|
|
73
41
|
{subTitle && <p className={`subtitle ${isRowLayout ? 'subtitle--text-start' : ''}`}>{subTitle}</p>}
|
|
74
42
|
{isButtonAvailable && renderButton()}
|
package/src/Common/Helper.tsx
CHANGED
|
@@ -499,18 +499,27 @@ export const getFilteredChartVersions = (charts, selectedChartType) => {
|
|
|
499
499
|
chartRefId: item.chartRefId,
|
|
500
500
|
}))
|
|
501
501
|
}
|
|
502
|
-
|
|
503
|
-
|
|
502
|
+
|
|
503
|
+
function removeEmptyObjectKeysAndNullValues(obj, originaljsonCopy) {
|
|
504
|
+
// It recursively removes empty object keys and empty array keys
|
|
504
505
|
for (let key in obj) {
|
|
505
506
|
if (Array.isArray(obj[key])) {
|
|
506
|
-
if (obj[key].length === 0) continue
|
|
507
|
-
obj[key] = obj[key].filter((item) => item !== null)
|
|
508
507
|
// Check if the array is empty
|
|
509
|
-
if (obj[key].length
|
|
510
|
-
|
|
508
|
+
if (obj[key].length !== 0) {
|
|
509
|
+
obj[key].forEach((element, index) => {
|
|
510
|
+
if (element === null || (typeof element === 'object' && Object.keys(element).length === 0))
|
|
511
|
+
obj[key].splice(index, 1)
|
|
512
|
+
else removeEmptyObjectKeysAndNullValues(element, originaljsonCopy[key][index])
|
|
513
|
+
})
|
|
514
|
+
}
|
|
515
|
+
if (obj[key].length === 0 && originaljsonCopy[key].length !== 0) {
|
|
516
|
+
delete obj[key]
|
|
511
517
|
}
|
|
512
518
|
} else if (obj[key] && typeof obj[key] === 'object') {
|
|
513
|
-
if (
|
|
519
|
+
if (
|
|
520
|
+
removeEmptyObjectKeysAndNullValues(obj[key], originaljsonCopy[key]) &&
|
|
521
|
+
Object.keys(originaljsonCopy[key]).length !== 0
|
|
522
|
+
) {
|
|
514
523
|
delete obj[key]
|
|
515
524
|
}
|
|
516
525
|
} else if (obj[key] === undefined) {
|
|
@@ -520,20 +529,25 @@ function removeEmptyObjectKeysAndNullValues(obj) {
|
|
|
520
529
|
return Object.keys(obj).length === 0
|
|
521
530
|
}
|
|
522
531
|
|
|
523
|
-
export function getUnlockedJSON(json, jsonPathArray) {
|
|
532
|
+
export function getUnlockedJSON(json, jsonPathArray, removeParentKeysAndEmptyArrays = false) {
|
|
533
|
+
if (!jsonPathArray.length) return { newDocument: json, removedPatches: [] }
|
|
534
|
+
|
|
524
535
|
const jsonCopy = JSON.parse(JSON.stringify(json))
|
|
536
|
+
const originaljsonCopy = JSON.parse(JSON.stringify(json))
|
|
537
|
+
let removedPatches = []
|
|
525
538
|
let patches = jsonPathArray.flatMap((jsonPath) => {
|
|
526
539
|
const pathsToRemove = JSONPath({ path: jsonPath, json: jsonCopy, resultType: 'all' })
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
540
|
+
//reversing patches to handle correct array index deletion
|
|
541
|
+
pathsToRemove.reverse()
|
|
542
|
+
return pathsToRemove.map((result) => {
|
|
543
|
+
//storing removed patches to have functionality of undo
|
|
544
|
+
removedPatches.push({ op: 'add', path: result.pointer, value: result.value })
|
|
545
|
+
return { op: 'remove', path: result.pointer }
|
|
546
|
+
})
|
|
532
547
|
})
|
|
533
548
|
let newDocument = jsonpatch.applyPatch(jsonCopy, patches).newDocument
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
return newDocument
|
|
549
|
+
if (removeParentKeysAndEmptyArrays) removeEmptyObjectKeysAndNullValues(newDocument, originaljsonCopy)
|
|
550
|
+
return { newDocument, removedPatches: removedPatches.reverse() }
|
|
537
551
|
}
|
|
538
552
|
|
|
539
553
|
export function getLockedJSON(json, jsonPathArray: string[]) {
|
|
@@ -559,3 +573,18 @@ export function getLockedJSON(json, jsonPathArray: string[]) {
|
|
|
559
573
|
return resultJson['$']
|
|
560
574
|
}
|
|
561
575
|
|
|
576
|
+
/**
|
|
577
|
+
* Returns a debounced variant of the function
|
|
578
|
+
*/
|
|
579
|
+
export const debounce = (func, timeout = 500) => {
|
|
580
|
+
let timer
|
|
581
|
+
|
|
582
|
+
return function (this, ...args) {
|
|
583
|
+
const context = this
|
|
584
|
+
if (timer) clearTimeout(timer)
|
|
585
|
+
timer = setTimeout(() => {
|
|
586
|
+
timer = null
|
|
587
|
+
func.apply(context, args)
|
|
588
|
+
}, timeout)
|
|
589
|
+
}
|
|
590
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { useRef, useState } from 'react'
|
|
2
|
+
import { PageSizeItemsProps, PageSizeOption, PageSizeSelectorProps } from './types'
|
|
3
|
+
import { useClickOutside } from '../Hooks'
|
|
4
|
+
import { getDefaultPageValueOptions } from './utils'
|
|
5
|
+
|
|
6
|
+
const PageSizeItems = ({
|
|
7
|
+
optionValue,
|
|
8
|
+
options,
|
|
9
|
+
setOptions,
|
|
10
|
+
handleCloseDropdown,
|
|
11
|
+
changePageSize,
|
|
12
|
+
}: PageSizeItemsProps) => {
|
|
13
|
+
const handlePageSizeSelect = () => {
|
|
14
|
+
const newOptions = options.map((_option) => ({
|
|
15
|
+
value: _option.value,
|
|
16
|
+
selected: _option.value === optionValue,
|
|
17
|
+
}))
|
|
18
|
+
setOptions([...newOptions])
|
|
19
|
+
handleCloseDropdown()
|
|
20
|
+
changePageSize(optionValue)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<div key={optionValue} className="select__item" onClick={handlePageSizeSelect}>
|
|
25
|
+
{optionValue}
|
|
26
|
+
</div>
|
|
27
|
+
)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const PageSizeSelector = ({ pageSizeOptions, pageSize, changePageSize }: PageSizeSelectorProps) => {
|
|
31
|
+
const defaultOptions = getDefaultPageValueOptions(pageSizeOptions).map((option) => ({
|
|
32
|
+
value: option.value,
|
|
33
|
+
selected: option.value === pageSize,
|
|
34
|
+
}))
|
|
35
|
+
|
|
36
|
+
const [isDropdownOpen, setIsDropdownOpen] = useState<boolean>(false)
|
|
37
|
+
const [options, setOptions] = useState<PageSizeOption[]>(defaultOptions)
|
|
38
|
+
|
|
39
|
+
const handleCloseDropdown = () => {
|
|
40
|
+
setIsDropdownOpen(false)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const handleDropdownToggle = () => {
|
|
44
|
+
setIsDropdownOpen(!isDropdownOpen)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const dropdownRef = useRef<HTMLDivElement>(null)
|
|
48
|
+
useClickOutside(dropdownRef, handleCloseDropdown)
|
|
49
|
+
|
|
50
|
+
const selectedOption = options.find((option) => option.selected)
|
|
51
|
+
const font = isDropdownOpen ? 'fa fa-caret-up' : 'fa fa-caret-down'
|
|
52
|
+
return (
|
|
53
|
+
<div ref={dropdownRef}>
|
|
54
|
+
{isDropdownOpen && (
|
|
55
|
+
<div className="pagination__select-menu">
|
|
56
|
+
{options.map((_option) => (
|
|
57
|
+
<PageSizeItems
|
|
58
|
+
optionValue={_option.value}
|
|
59
|
+
options={options}
|
|
60
|
+
setOptions={setOptions}
|
|
61
|
+
handleCloseDropdown={handleCloseDropdown}
|
|
62
|
+
changePageSize={changePageSize}
|
|
63
|
+
/>
|
|
64
|
+
))}
|
|
65
|
+
</div>
|
|
66
|
+
)}
|
|
67
|
+
|
|
68
|
+
<button type="button" className="select__button" onClick={handleDropdownToggle}>
|
|
69
|
+
<span>{selectedOption ? selectedOption.value : ''}</span>
|
|
70
|
+
<span className="select__icon">
|
|
71
|
+
<i className={font} />
|
|
72
|
+
</span>
|
|
73
|
+
</button>
|
|
74
|
+
</div>
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export default PageSizeSelector
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react'
|
|
2
|
+
import PageSizeSelector from './PageSizeSelector'
|
|
3
|
+
import { Page, PageValueItemProps, PaginationProps } from './types'
|
|
4
|
+
import { createPageArr } from './utils'
|
|
5
|
+
import './pagination.scss'
|
|
6
|
+
|
|
7
|
+
const PageValueItem = ({ value, isSelected, selectPage }: PageValueItemProps) => {
|
|
8
|
+
const classes = isSelected ? 'page__button page__button--selected' : 'page__button'
|
|
9
|
+
const handlePageSelection = () => {
|
|
10
|
+
selectPage(value)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<li className="page">
|
|
15
|
+
<button className={classes} onClick={handlePageSelection} type="button">
|
|
16
|
+
{value}
|
|
17
|
+
</button>
|
|
18
|
+
</li>
|
|
19
|
+
)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// NOTE: This component will heavily benefit from data normalization
|
|
23
|
+
const Pagination = ({
|
|
24
|
+
size,
|
|
25
|
+
pageSize,
|
|
26
|
+
offset,
|
|
27
|
+
changePage,
|
|
28
|
+
changePageSize,
|
|
29
|
+
isPageSizeFix,
|
|
30
|
+
pageSizeOptions,
|
|
31
|
+
rootClassName,
|
|
32
|
+
}: PaginationProps) => {
|
|
33
|
+
const [pages, setPages] = useState<Page[]>([])
|
|
34
|
+
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
const _pages = createPageArr({ size, pageSize, selectedPageNo: 1 })
|
|
37
|
+
setPages(_pages)
|
|
38
|
+
}, [size, pageSize])
|
|
39
|
+
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
const selectedPageNo = 1 + Math.floor(offset / pageSize)
|
|
42
|
+
const _pages = createPageArr({ size, pageSize, selectedPageNo })
|
|
43
|
+
setPages(_pages)
|
|
44
|
+
}, [offset])
|
|
45
|
+
|
|
46
|
+
const selectPage = (selectedPageNo: number): void => {
|
|
47
|
+
const _pages = createPageArr({ size, pageSize, selectedPageNo })
|
|
48
|
+
setPages(_pages)
|
|
49
|
+
changePage(selectedPageNo)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const selectPreviousPage = (): void => {
|
|
53
|
+
const currPage = pages.find((page) => page.selected)
|
|
54
|
+
const selectedPageNo = currPage.value - 1
|
|
55
|
+
selectPage(selectedPageNo)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const selectNextPage = (): void => {
|
|
59
|
+
const currPage = pages.find((page) => page.selected)
|
|
60
|
+
const selectedPageNo = currPage.value + 1
|
|
61
|
+
selectPage(selectedPageNo)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const lastPageNo = offset + pageSize < size ? offset + pageSize : size
|
|
65
|
+
const pageNoIndex = pages.findIndex((page) => page.selected)
|
|
66
|
+
const visiblePages = pages.filter((page) => page.isVisible)
|
|
67
|
+
return (
|
|
68
|
+
// TODO: Remove pagination-wrapper from dashboard
|
|
69
|
+
<div className={rootClassName || ''}>
|
|
70
|
+
<div className="page-number">
|
|
71
|
+
{offset + 1} - {lastPageNo} of {size}
|
|
72
|
+
</div>
|
|
73
|
+
|
|
74
|
+
<ul className="pagination">
|
|
75
|
+
<li className="page">
|
|
76
|
+
<button
|
|
77
|
+
className="page__button page__button--icon"
|
|
78
|
+
disabled={!pageNoIndex}
|
|
79
|
+
onClick={selectPreviousPage}
|
|
80
|
+
type="button"
|
|
81
|
+
aria-label="Previous page"
|
|
82
|
+
>
|
|
83
|
+
<span className="left-icon">
|
|
84
|
+
<i className="fa fa-chevron-left" />
|
|
85
|
+
</span>
|
|
86
|
+
</button>
|
|
87
|
+
</li>
|
|
88
|
+
|
|
89
|
+
{visiblePages.map((page) => (
|
|
90
|
+
<PageValueItem
|
|
91
|
+
value={page.value}
|
|
92
|
+
isSelected={page.selected}
|
|
93
|
+
key={page.value}
|
|
94
|
+
selectPage={selectPage}
|
|
95
|
+
/>
|
|
96
|
+
))}
|
|
97
|
+
|
|
98
|
+
<li className="page">
|
|
99
|
+
<button
|
|
100
|
+
className="page__button page__button--icon"
|
|
101
|
+
disabled={pageNoIndex === pages.length - 1}
|
|
102
|
+
onClick={selectNextPage}
|
|
103
|
+
type="button"
|
|
104
|
+
aria-label="Next page"
|
|
105
|
+
>
|
|
106
|
+
<span className="left-icon">
|
|
107
|
+
<i className="fa fa-chevron-right" />
|
|
108
|
+
</span>
|
|
109
|
+
</button>
|
|
110
|
+
</li>
|
|
111
|
+
</ul>
|
|
112
|
+
|
|
113
|
+
{!isPageSizeFix && (
|
|
114
|
+
<div className="select">
|
|
115
|
+
<span className="select__placeholder">Rows per page</span>
|
|
116
|
+
<PageSizeSelector
|
|
117
|
+
pageSizeOptions={pageSizeOptions}
|
|
118
|
+
pageSize={pageSize}
|
|
119
|
+
changePageSize={changePageSize}
|
|
120
|
+
/>
|
|
121
|
+
</div>
|
|
122
|
+
)}
|
|
123
|
+
</div>
|
|
124
|
+
)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export default Pagination
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
.pagination-wrapper {
|
|
2
|
+
display: flex;
|
|
3
|
+
align-items: center;
|
|
4
|
+
padding: 0px 24px;
|
|
5
|
+
justify-content: space-between;
|
|
6
|
+
margin: 0px;
|
|
7
|
+
background-color: var(--white);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.pagination {
|
|
11
|
+
margin-top: 16px;
|
|
12
|
+
margin-bottom: 16px;
|
|
13
|
+
padding: 0px;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.select {
|
|
17
|
+
display: flex;
|
|
18
|
+
align-items: center;
|
|
19
|
+
position: relative;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.select__placeholder {
|
|
23
|
+
font-size: 12px;
|
|
24
|
+
font-weight: normal;
|
|
25
|
+
font-style: normal;
|
|
26
|
+
font-stretch: normal;
|
|
27
|
+
line-height: 1.5;
|
|
28
|
+
letter-spacing: normal;
|
|
29
|
+
color: var(--N700);
|
|
30
|
+
margin-right: 8px;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.pagination__select-menu {
|
|
34
|
+
padding: 0px;
|
|
35
|
+
list-style: none;
|
|
36
|
+
overflow: hidden;
|
|
37
|
+
border: solid 1px var(--N200);
|
|
38
|
+
border-radius: 4px;
|
|
39
|
+
width: 64px;
|
|
40
|
+
position: absolute;
|
|
41
|
+
bottom: 31px;
|
|
42
|
+
background-color: var(--white);;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.select__button {
|
|
46
|
+
width: 64px;
|
|
47
|
+
background-color: transparent;
|
|
48
|
+
border-radius: 4px;
|
|
49
|
+
border: solid 1px var(--N200);
|
|
50
|
+
padding: 5px 10px;
|
|
51
|
+
text-align: left;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.select__item {
|
|
55
|
+
padding: 5px 10px;
|
|
56
|
+
cursor: pointer;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.select__icon {
|
|
60
|
+
margin-left: 18px;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.page {
|
|
64
|
+
margin: 0px 8px;
|
|
65
|
+
display: inline-block;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.page-number {
|
|
69
|
+
font-size: 12px;
|
|
70
|
+
font-weight: normal;
|
|
71
|
+
font-style: normal;
|
|
72
|
+
font-stretch: normal;
|
|
73
|
+
line-height: 1.5;
|
|
74
|
+
letter-spacing: normal;
|
|
75
|
+
color: var(--N700);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.page__button {
|
|
79
|
+
width: 24px;
|
|
80
|
+
height: 24px;
|
|
81
|
+
border-radius: 8px;
|
|
82
|
+
color: var(--B500);
|
|
83
|
+
background-color: var(--white);
|
|
84
|
+
padding: 0px;
|
|
85
|
+
user-select: none;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.page__button--icon {
|
|
89
|
+
background-color: var(--white);
|
|
90
|
+
color: var(--B500);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/* MODIFIERS */
|
|
94
|
+
|
|
95
|
+
.page__button--selected {
|
|
96
|
+
color: var(--white);
|
|
97
|
+
background-color: var(--B500);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/* States and Selectorys */
|
|
101
|
+
|
|
102
|
+
.page__button:disabled {
|
|
103
|
+
opacity: 0.5;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.select__item:hover {
|
|
107
|
+
background-color: var(--N50);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.page__button:hover ~ :disabled {
|
|
111
|
+
background-color: var(--B100);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.page__button--selected:hover {
|
|
115
|
+
color: var(--white);
|
|
116
|
+
background-color: var(--B500);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
@media (min-width: 1024px) {
|
|
120
|
+
.pagination {
|
|
121
|
+
margin: 16px auto;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
export interface PageSizeOption {
|
|
2
|
+
value: number
|
|
3
|
+
selected: boolean
|
|
4
|
+
}
|
|
5
|
+
export interface PaginationProps {
|
|
6
|
+
/**
|
|
7
|
+
* number of items
|
|
8
|
+
*/
|
|
9
|
+
size: number
|
|
10
|
+
/**
|
|
11
|
+
* pageSize
|
|
12
|
+
*/
|
|
13
|
+
pageSize: number
|
|
14
|
+
offset: number
|
|
15
|
+
changePage: (pageNo: number) => void
|
|
16
|
+
changePageSize?: (pageSize: number) => void
|
|
17
|
+
/**
|
|
18
|
+
* If true will not show page size selector
|
|
19
|
+
*/
|
|
20
|
+
isPageSizeFix?: boolean
|
|
21
|
+
/**
|
|
22
|
+
* If given would show these options in page size selector else [20,40,50] with 20 as selected
|
|
23
|
+
*/
|
|
24
|
+
pageSizeOptions?: PageSizeOption[]
|
|
25
|
+
/**
|
|
26
|
+
* class for wrapper
|
|
27
|
+
*/
|
|
28
|
+
rootClassName?: string
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface Page {
|
|
32
|
+
value: number
|
|
33
|
+
selected: boolean
|
|
34
|
+
isVisible: boolean
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface CreatePageArrType extends Pick<PaginationProps, 'size' | 'pageSize'> {
|
|
38
|
+
selectedPageNo: number
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface PageSizeSelectorProps
|
|
42
|
+
extends Pick<PaginationProps, 'pageSizeOptions' | 'pageSize' | 'changePageSize'> {}
|
|
43
|
+
|
|
44
|
+
export interface PageSizeItemsProps extends Pick<PageSizeSelectorProps, 'changePageSize'> {
|
|
45
|
+
optionValue: PageSizeOption['value']
|
|
46
|
+
options: PageSizeOption[]
|
|
47
|
+
setOptions: React.Dispatch<React.SetStateAction<PageSizeOption[]>>
|
|
48
|
+
handleCloseDropdown: () => void
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface PageValueItemProps {
|
|
52
|
+
value: PageSizeOption['value']
|
|
53
|
+
isSelected: PageSizeOption['selected']
|
|
54
|
+
selectPage: (selectedPageNo: number) => void
|
|
55
|
+
}
|