@js-smart/react-kit 5.11.0 → 5.12.0-beta.1

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.
Files changed (135) hide show
  1. package/.editorconfig +13 -0
  2. package/.eslintignore +1 -0
  3. package/.eslintrc.json +41 -0
  4. package/.github/copilot-instructions.md +11 -0
  5. package/.github/workflows/build.yml +45 -0
  6. package/.github/workflows/release.yml +65 -0
  7. package/.prettierignore +5 -0
  8. package/.prettierrc +9 -0
  9. package/.vscode/extensions.json +7 -0
  10. package/CHANGELOG.md +24 -0
  11. package/CODE_OF_CONDUCT.md +128 -0
  12. package/FUNDING.yml +1 -0
  13. package/LICENSE +21 -0
  14. package/README.md +1 -0
  15. package/apps/react-kit-demo/.eslintrc.json +22 -0
  16. package/apps/react-kit-demo/index.html +16 -0
  17. package/apps/react-kit-demo/project.json +9 -0
  18. package/apps/react-kit-demo/public/favicon.ico +0 -0
  19. package/apps/react-kit-demo/src/app/Home.tsx +17 -0
  20. package/apps/react-kit-demo/src/app/all-books/AllBooks.tsx +68 -0
  21. package/apps/react-kit-demo/src/app/app.module.scss +1 -0
  22. package/apps/react-kit-demo/src/app/app.tsx +29 -0
  23. package/apps/react-kit-demo/src/app/buttons/ButtonsDemo.tsx +58 -0
  24. package/apps/react-kit-demo/src/app/dialog/DialogDemo.tsx +23 -0
  25. package/apps/react-kit-demo/src/app/links/LinksDemo.tsx +20 -0
  26. package/apps/react-kit-demo/src/app/progress-bar/CenterCircularProgressDemo.tsx +10 -0
  27. package/apps/react-kit-demo/src/app/react-if/ReactIfDemo.tsx +44 -0
  28. package/apps/react-kit-demo/src/app/snack-bar/SnackBarDemo.tsx +35 -0
  29. package/apps/react-kit-demo/src/assets/.gitkeep +0 -0
  30. package/apps/react-kit-demo/src/constants/ApiConstants.ts +7 -0
  31. package/apps/react-kit-demo/src/constants/DialogMode.ts +2 -0
  32. package/apps/react-kit-demo/src/constants/HttpConstants.ts +18 -0
  33. package/apps/react-kit-demo/src/constants/StateConstants.ts +2 -0
  34. package/apps/react-kit-demo/src/main.tsx +17 -0
  35. package/apps/react-kit-demo/src/routes/Routes.tsx +55 -0
  36. package/apps/react-kit-demo/src/services/BookService.ts +21 -0
  37. package/apps/react-kit-demo/src/styles.scss +36 -0
  38. package/apps/react-kit-demo/src/theme.ts +46 -0
  39. package/apps/react-kit-demo/src/types/Book.ts +8 -0
  40. package/{lib/utils/CssUtils.d.ts → apps/react-kit-demo/src/utils/CssUtils.ts} +3 -1
  41. package/apps/react-kit-demo/tsconfig.app.json +18 -0
  42. package/apps/react-kit-demo/tsconfig.json +21 -0
  43. package/apps/react-kit-demo/tsconfig.spec.json +28 -0
  44. package/apps/react-kit-demo/vite.config.ts +50 -0
  45. package/nx.json +52 -0
  46. package/package.json +99 -44
  47. package/react-kit/.babelrc +12 -0
  48. package/react-kit/.eslintrc.json +18 -0
  49. package/react-kit/README.md +7 -0
  50. package/react-kit/package-lock.json +1330 -0
  51. package/react-kit/package.json +45 -0
  52. package/react-kit/project.json +10 -0
  53. package/{index.d.ts → react-kit/src/index.ts} +9 -0
  54. package/react-kit/src/lib/components/CenteredCircularProgress.tsx +15 -0
  55. package/react-kit/src/lib/components/ConfirmationDialog.tsx +28 -0
  56. package/react-kit/src/lib/components/DismissibleAlert.tsx +60 -0
  57. package/react-kit/src/lib/components/NextLink.tsx +26 -0
  58. package/react-kit/src/lib/components/OpenInNewIconLink.tsx +42 -0
  59. package/react-kit/src/lib/components/ReactIf.tsx +53 -0
  60. package/react-kit/src/lib/components/buttons/CancelButton.tsx +45 -0
  61. package/react-kit/src/lib/components/buttons/DeleteButton.tsx +35 -0
  62. package/react-kit/src/lib/components/buttons/EditIconButton.tsx +23 -0
  63. package/react-kit/src/lib/components/buttons/ExcelButton.tsx +43 -0
  64. package/react-kit/src/lib/components/buttons/GoBackButton.tsx +22 -0
  65. package/react-kit/src/lib/components/buttons/HistoryButton.tsx +45 -0
  66. package/react-kit/src/lib/components/buttons/LoadingSuccessButton.tsx +53 -0
  67. package/react-kit/src/lib/components/buttons/ManageButton.tsx +31 -0
  68. package/react-kit/src/lib/components/buttons/SuccessButton.tsx +44 -0
  69. package/react-kit/src/lib/components/snack-bar/AppSnackBar.tsx +46 -0
  70. package/react-kit/src/lib/components/snack-bar/QuerySnackBar.tsx +62 -0
  71. package/react-kit/src/lib/components/table/TablePaginationActions.tsx +58 -0
  72. package/react-kit/src/lib/components/tabs/TabPanel.tsx +26 -0
  73. package/react-kit/src/lib/constants/AppConstants.ts +17 -0
  74. package/react-kit/src/lib/types/ProgressState.ts +7 -0
  75. package/react-kit/src/lib/utils/BooleanUtils.ts +13 -0
  76. package/react-kit/src/lib/utils/CssUtils.ts +13 -0
  77. package/react-kit/src/lib/utils/DateUtils.ts +43 -0
  78. package/react-kit/src/lib/utils/NumberUtils.ts +12 -0
  79. package/react-kit/src/lib/utils/ProgressStateUtils.ts +68 -0
  80. package/react-kit/src/lib/utils/StringUtils.ts +14 -0
  81. package/react-kit/src/lib/utils/UrlUtils.ts +19 -0
  82. package/react-kit/src/lib/utils/fetchClient.ts +64 -0
  83. package/react-kit/src/tests/buttons/CancelButton.test.tsx +69 -0
  84. package/react-kit/src/tests/buttons/DeleteButton.test.tsx +63 -0
  85. package/react-kit/src/tests/buttons/EditIconButton.test.tsx +34 -0
  86. package/react-kit/src/tests/buttons/HistoryButton.test.tsx +46 -0
  87. package/react-kit/src/tests/buttons/LoadingSuccessButton.test.tsx +53 -0
  88. package/react-kit/src/tests/buttons/ManageButton.test.tsx +49 -0
  89. package/react-kit/src/tests/buttons/SuccessButton.test.tsx +46 -0
  90. package/react-kit/src/tests/snack-bar/AppSnackBar.test.tsx +54 -0
  91. package/react-kit/src/tests/utils/BooleanUtils.test.ts +35 -0
  92. package/react-kit/src/tests/utils/CssUtils.test.ts +17 -0
  93. package/react-kit/src/tests/utils/DateUtils.test.ts +46 -0
  94. package/react-kit/src/tests/utils/NumberUtils.test.ts +19 -0
  95. package/react-kit/src/tests/utils/ProgressStateUtils.test.ts +131 -0
  96. package/react-kit/src/tests/utils/StringUtils.test.ts +33 -0
  97. package/react-kit/src/tests/utils/UrlUtils.test.ts +19 -0
  98. package/react-kit/tsconfig.json +22 -0
  99. package/react-kit/tsconfig.lib.json +24 -0
  100. package/react-kit/tsconfig.spec.json +27 -0
  101. package/react-kit/vite.config.ts +72 -0
  102. package/release.sh +28 -0
  103. package/tsconfig.base.json +22 -0
  104. package/vitest.workspace.js +3 -0
  105. package/vitest.workspace.ts +1 -0
  106. package/index.cjs +0 -74
  107. package/index.js +0 -4120
  108. package/lib/components/CenteredCircularProgress.d.ts +0 -7
  109. package/lib/components/ConfirmationDialog.d.ts +0 -11
  110. package/lib/components/DismissibleAlert.d.ts +0 -17
  111. package/lib/components/NextLink.d.ts +0 -17
  112. package/lib/components/OpenInNewIconLink.d.ts +0 -17
  113. package/lib/components/ReactIf.d.ts +0 -36
  114. package/lib/components/buttons/CancelButton.d.ts +0 -28
  115. package/lib/components/buttons/DeleteButton.d.ts +0 -16
  116. package/lib/components/buttons/EditIconButton.d.ts +0 -8
  117. package/lib/components/buttons/ExcelButton.d.ts +0 -26
  118. package/lib/components/buttons/GoBackButton.d.ts +0 -10
  119. package/lib/components/buttons/HistoryButton.d.ts +0 -28
  120. package/lib/components/buttons/LoadingSuccessButton.d.ts +0 -28
  121. package/lib/components/buttons/ManageButton.d.ts +0 -14
  122. package/lib/components/buttons/SuccessButton.d.ts +0 -28
  123. package/lib/components/snack-bar/AppSnackBar.d.ts +0 -6
  124. package/lib/components/snack-bar/QuerySnackBar.d.ts +0 -18
  125. package/lib/components/table/TablePaginationActions.d.ts +0 -9
  126. package/lib/components/tabs/TabPanel.d.ts +0 -12
  127. package/lib/constants/AppConstants.d.ts +0 -15
  128. package/lib/types/ProgressState.d.ts +0 -7
  129. package/lib/utils/BooleanUtils.d.ts +0 -7
  130. package/lib/utils/DateUtils.d.ts +0 -22
  131. package/lib/utils/NumberUtils.d.ts +0 -7
  132. package/lib/utils/ProgressStateUtils.d.ts +0 -38
  133. package/lib/utils/StringUtils.d.ts +0 -7
  134. package/lib/utils/UrlUtils.d.ts +0 -11
  135. package/lib/utils/fetchClient.d.ts +0 -12
@@ -0,0 +1,46 @@
1
+ import CloseIcon from '@mui/icons-material/Close';
2
+ import { Alert, IconButton, Slide, Snackbar } from '@mui/material';
3
+ import { useEffect, useState } from 'react';
4
+ import { ProgressState } from '../../types/ProgressState';
5
+
6
+ export const AppSnackBar = (props: { open: boolean; progressState: ProgressState; autoHideDuration?: number }) => {
7
+ const [open, setOpen] = useState(false);
8
+
9
+ useEffect(() => {
10
+ setOpen(props.open);
11
+ }, [props.open, props.progressState]);
12
+
13
+ // Close button
14
+ const action = (
15
+ <IconButton size="small" aria-label="close" color="inherit" onClick={() => setOpen(false)}>
16
+ <CloseIcon fontSize="small" />
17
+ </IconButton>
18
+ );
19
+
20
+ return (
21
+ <>
22
+ {/* Success Alert */}
23
+ <Snackbar
24
+ anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
25
+ open={open && props.progressState.isSuccess}
26
+ autoHideDuration={props.autoHideDuration ?? 3000}
27
+ onClose={() => setOpen(false)}>
28
+ <Alert variant="filled" severity="success" sx={{ width: '100%' }} action={action}>
29
+ {props.progressState.message}
30
+ </Alert>
31
+ </Snackbar>
32
+
33
+ {/* Error Alert */}
34
+ <Snackbar
35
+ anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
36
+ open={open && props.progressState.isError}
37
+ TransitionComponent={Slide}
38
+ autoHideDuration={props.autoHideDuration ?? 3000}
39
+ onClose={() => setOpen(false)}>
40
+ <Alert variant="filled" sx={{ width: '100%' }} severity="error" action={action}>
41
+ {props.progressState.message}
42
+ </Alert>
43
+ </Snackbar>
44
+ </>
45
+ );
46
+ };
@@ -0,0 +1,62 @@
1
+ import CloseIcon from '@mui/icons-material/Close';
2
+ import { Alert, IconButton, Slide, Snackbar } from '@mui/material';
3
+ import { useEffect, useState } from 'react';
4
+
5
+ interface QuerySnackBarProps {
6
+ open: boolean;
7
+ isPending?: boolean;
8
+ isSuccess: boolean;
9
+ isError: boolean;
10
+ message: string;
11
+ autoHideDuration?: number;
12
+ }
13
+
14
+ /**
15
+ * Reusable component to display snackbar based on React Query mutation state
16
+ *
17
+ * @param props QuerySnackBarProps
18
+ *
19
+ * @author Pavan Kumar Jadda
20
+ * @since 1.2.27
21
+ */
22
+ export const QuerySnackBar = (props: QuerySnackBarProps) => {
23
+ const [open, setOpen] = useState(false);
24
+
25
+ useEffect(() => {
26
+ setOpen(props.open);
27
+ }, [props.open, props]);
28
+
29
+ // Close button
30
+ const action = (
31
+ <IconButton size="small" aria-label="close" color="inherit" onClick={() => setOpen(false)}>
32
+ <CloseIcon fontSize="small" />
33
+ </IconButton>
34
+ );
35
+
36
+ return (
37
+ <>
38
+ {/* Success Alert */}
39
+ <Snackbar
40
+ anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
41
+ open={open && props.isSuccess}
42
+ autoHideDuration={props.autoHideDuration ?? 3000}
43
+ onClose={() => setOpen(false)}>
44
+ <Alert variant="filled" severity="success" sx={{ width: '100%' }} action={action}>
45
+ {props.message}
46
+ </Alert>
47
+ </Snackbar>
48
+
49
+ {/* Error Alert */}
50
+ <Snackbar
51
+ anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
52
+ open={open && props.isError}
53
+ TransitionComponent={Slide}
54
+ autoHideDuration={props.autoHideDuration ?? 3000}
55
+ onClose={() => setOpen(false)}>
56
+ <Alert variant="filled" sx={{ width: '100%' }} severity="error" action={action}>
57
+ {props.message}
58
+ </Alert>
59
+ </Snackbar>
60
+ </>
61
+ );
62
+ };
@@ -0,0 +1,58 @@
1
+ import React from 'react';
2
+ import { Box, IconButton } from '@mui/material';
3
+ import FirstPageIcon from '@mui/icons-material/FirstPage';
4
+ import KeyboardArrowLeft from '@mui/icons-material/KeyboardArrowLeft';
5
+ import KeyboardArrowRight from '@mui/icons-material/KeyboardArrowRight';
6
+ import LastPageIcon from '@mui/icons-material/LastPage';
7
+ import { useTheme } from '@mui/material/styles';
8
+
9
+ interface TablePaginationActionsProps {
10
+ count: number;
11
+ page: number;
12
+ rowsPerPage: number;
13
+ onPageChange: (event: React.MouseEvent<HTMLButtonElement>, newPage: number) => void;
14
+ }
15
+
16
+ /*
17
+ * Reusable component that adds first and last action buttons to DataGrid pagination module
18
+ *
19
+ * @author Pavan Kumar Jadda
20
+ * @since 1.1.4
21
+ */
22
+ export function TablePaginationActions(props: TablePaginationActionsProps) {
23
+ const theme = useTheme();
24
+ const { count, page, rowsPerPage, onPageChange } = props;
25
+
26
+ const handleFirstPageButtonClick = (event: React.MouseEvent<HTMLButtonElement>) => {
27
+ onPageChange(event, 0);
28
+ };
29
+
30
+ const handleBackButtonClick = (event: React.MouseEvent<HTMLButtonElement>) => {
31
+ onPageChange(event, page - 1);
32
+ };
33
+
34
+ const handleNextButtonClick = (event: React.MouseEvent<HTMLButtonElement>) => {
35
+ onPageChange(event, page + 1);
36
+ };
37
+
38
+ const handleLastPageButtonClick = (event: React.MouseEvent<HTMLButtonElement>) => {
39
+ onPageChange(event, Math.max(0, Math.ceil(count / rowsPerPage) - 1));
40
+ };
41
+
42
+ return (
43
+ <Box sx={{ flexShrink: 0, ml: 2.5 }}>
44
+ <IconButton onClick={handleFirstPageButtonClick} disabled={page === 0} aria-label="first page">
45
+ {theme.direction === 'rtl' ? <LastPageIcon /> : <FirstPageIcon />}
46
+ </IconButton>
47
+ <IconButton onClick={handleBackButtonClick} disabled={page === 0} aria-label="previous page">
48
+ {theme.direction === 'rtl' ? <KeyboardArrowRight /> : <KeyboardArrowLeft />}
49
+ </IconButton>
50
+ <IconButton onClick={handleNextButtonClick} disabled={page >= Math.ceil(count / rowsPerPage) - 1} aria-label="next page">
51
+ {theme.direction === 'rtl' ? <KeyboardArrowLeft /> : <KeyboardArrowRight />}
52
+ </IconButton>
53
+ <IconButton onClick={handleLastPageButtonClick} disabled={page >= Math.ceil(count / rowsPerPage) - 1} aria-label="last page">
54
+ {theme.direction === 'rtl' ? <FirstPageIcon /> : <LastPageIcon />}
55
+ </IconButton>
56
+ </Box>
57
+ );
58
+ }
@@ -0,0 +1,26 @@
1
+ import React from 'react';
2
+ import { Box, Typography } from '@mui/material';
3
+
4
+ export function TabPanel(props: TabPanelProps) {
5
+ const { children, index, ...other } = props;
6
+ return (
7
+ <div className="tabPanel" role="tabpanel" id={`vertical-tabpanel-${index}`} aria-labelledby={`vertical-tab-${index}`} {...other}>
8
+ <Box sx={{ p: 3 }}>
9
+ <Typography component={'span'}>{children}</Typography>
10
+ </Box>
11
+ </div>
12
+ );
13
+ }
14
+
15
+ export function a11yProps(index: number) {
16
+ return {
17
+ id: `vertical-tab-${index}`,
18
+ 'aria-controls': `vertical-tabpanel-${index}`,
19
+ };
20
+ }
21
+
22
+ interface TabPanelProps {
23
+ children?: React.ReactNode;
24
+ index: number;
25
+ value: string;
26
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Global System settings used in the application
3
+ *
4
+ * @author Pavan Kumar Jadda
5
+ * @since 1.0.0
6
+ */
7
+ export enum SystemConfig {
8
+ SYSTEM_TIME_ZONE = 'America/New_York',
9
+ SYSTEM_LOCALE = 'en-US',
10
+ SYSTEM_DATE_FORMAT = 'MM/dd/yyyy',
11
+ SYSTEM_DATE_TIME_FORMAT = 'MM/dd/yyyy hh:mm:ss a',
12
+ ISO_DATE_FORMAT = 'yyyy-MM-dd',
13
+
14
+ //Default Cookie expiration is 24 hours(Server sends usually cookie with 60 minutes timeout)
15
+ SYSTEM_COOKIE_TIMEOUT_HOURS = 24,
16
+ SYSTEM_COOKIE_TIMEOUT_MILLI_SECONDS = 3600000,
17
+ }
@@ -0,0 +1,7 @@
1
+ export interface ProgressState {
2
+ isLoading: boolean;
3
+ isSuccess: boolean;
4
+ isError: boolean;
5
+ isComplete: boolean;
6
+ message: string;
7
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Returns `true` if the provided string is `undefined`, `null` or empty '' string otherwise returns false
3
+ *
4
+ * @author Pavan Kumar Jadda
5
+ * @since 0.1.0
6
+ */
7
+ export function parseBoolean(value: boolean | string | null | undefined): boolean {
8
+ if (typeof value === 'boolean') {
9
+ return value;
10
+ } else if (typeof value === 'string') {
11
+ return value.toLowerCase() === 'true';
12
+ } else return false;
13
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Get the value of a CSS variable
3
+ *
4
+ * @param variable The name of the CSS variable
5
+ *
6
+ * @returns The value of the CSS variable
7
+ *
8
+ * @author Pavan Kumar Jadda
9
+ * @since 1.6.0
10
+ */
11
+ export function getCssVariable(variable: string): string {
12
+ return getComputedStyle(document.documentElement).getPropertyValue(variable).trim();
13
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Utility class for date related operations.
3
+ *
4
+ * @author Pavan Kumar Jadda
5
+ * @since 0.1.6
6
+ */
7
+ import { format, parseISO } from 'date-fns';
8
+ import { SystemConfig } from '../constants/AppConstants';
9
+
10
+ /**
11
+ * Sets Cookie expiration to 24 hours. By default, server sets 60 minutes expiration but after each API request it extends to another 60 minutes. In client side set 24 hours as expiration date,
12
+ * if the user hasn't refreshed web page in 60 minutes, they would get HTTP 401 isError and redirected to login page. And redirect URL will be stored in cookie
13
+ *
14
+ * @author Pavan Kumar Jadda
15
+ * @since 0.2.30
16
+ */
17
+ export const setCookieExpirationDate = (): Date => {
18
+ const utcEpochTime = +new Date();
19
+ return new Date(utcEpochTime + SystemConfig.SYSTEM_COOKIE_TIMEOUT_MILLI_SECONDS);
20
+ };
21
+
22
+ /**
23
+ * Convert String format browser Date Time to ISO Date
24
+ *
25
+ * @author Pavan Kumar Jadda
26
+ * @since 0.2.30
27
+ */
28
+ export const convertToIsoDate = (currentDateTime: string): string => {
29
+ return format(new Date(currentDateTime), SystemConfig.ISO_DATE_FORMAT);
30
+ };
31
+
32
+ /**
33
+ * Convert String format browser Date Time to ISO Date
34
+ *
35
+ * @author Pavan Kumar Jadda
36
+ * @since 0.2.30
37
+ */
38
+ export const formatDate = (date: string | undefined, newFormat: string): string => {
39
+ if (!date) {
40
+ return '';
41
+ }
42
+ return format(parseISO(date), newFormat);
43
+ };
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Returns number parsed from the given string
3
+ *
4
+ * @author Pavan Kumar Jadda
5
+ * @since 0.3.5
6
+ */
7
+ export function parseNumber(value: string | null | undefined): number | undefined {
8
+ if (typeof value === 'string') {
9
+ return Number.parseInt(value, 10);
10
+ }
11
+ return undefined;
12
+ }
@@ -0,0 +1,68 @@
1
+ import { ProgressState } from '../types/ProgressState';
2
+
3
+ /**
4
+ * Initialize Loading or Update ProgressState
5
+ *
6
+ * @return Updated State Object
7
+ *
8
+ * @author Pavan Kumar Jadda
9
+ * @since 1.4.6
10
+ */
11
+ export const initializeState = (): ProgressState => ({
12
+ isLoading: false,
13
+ isSuccess: false,
14
+ isError: false,
15
+ isComplete: false,
16
+ message: '',
17
+ });
18
+
19
+ /**
20
+ * Initialize Loading or Update ProgressState
21
+ *
22
+ * @param progressState Object to initialize
23
+ * @return ProgressState Updated State Object
24
+ *
25
+ * @author Pavan Kumar Jadda
26
+ * @since 0.2.30
27
+ */
28
+ export const markLoading = (progressState: ProgressState): ProgressState => ({
29
+ ...progressState,
30
+ isLoading: true,
31
+ isSuccess: false,
32
+ isError: false,
33
+ message: '',
34
+ });
35
+
36
+ /**
37
+ * Update state as isSuccess
38
+ *
39
+ * @return ProgressState Updated State Object
40
+ *
41
+ * @author Pavan Kumar Jadda
42
+ * @since 0.2.30
43
+ */
44
+ export const markSuccess = (progressState: ProgressState, message?: string): ProgressState => ({
45
+ ...progressState,
46
+ isLoading: false,
47
+ isSuccess: true,
48
+ isError: false,
49
+ isComplete: true,
50
+ message: message || '',
51
+ });
52
+
53
+ /**
54
+ * Update state as failure or isError
55
+ *
56
+ * @return ProgressState Updated State Object
57
+ *
58
+ * @author Pavan Kumar Jadda
59
+ * @since 0.2.30
60
+ */
61
+ export const markError = (progressState: ProgressState, message?: string): ProgressState => ({
62
+ ...progressState,
63
+ isLoading: false,
64
+ isSuccess: false,
65
+ isError: true,
66
+ isComplete: true,
67
+ message: message || '',
68
+ });
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Returns `true` if the provided string is `undefined`, `null` or empty '' string otherwise returns false
3
+ *
4
+ * @author Pavan Kumar Jadda
5
+ * @since 0.1.0
6
+ */
7
+ export function isBlankOrEmpty(value: any): boolean {
8
+ if (value === null || value === undefined) {
9
+ return true;
10
+ } else if (typeof value === 'string') {
11
+ return value.trim() === '';
12
+ }
13
+ return false;
14
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Checks if the given URL is encoded or not
3
+ *
4
+ * @param url The URL to check
5
+ *
6
+ * @returns True if the URL is encoded, false otherwise
7
+ *
8
+ * @author Pavan Kumar Jadda
9
+ * @since 1.3.14
10
+ */
11
+ export const isEncoded = (url: string) => {
12
+ try {
13
+ const decodedUrl = decodeURIComponent(url);
14
+ return decodedUrl !== url;
15
+ } catch (error) {
16
+ // Return false if decoding fails, indicating the URL is not properly encoded
17
+ return false;
18
+ }
19
+ };
@@ -0,0 +1,64 @@
1
+ class HttpError extends Error {
2
+ status: number;
3
+ statusText: string;
4
+ redirected: boolean;
5
+ type: ResponseType | undefined;
6
+
7
+ constructor(message: string, { status, statusText, redirected, type }: Response) {
8
+ super(message);
9
+ this.status = status;
10
+ this.statusText = statusText;
11
+ this.statusText = type;
12
+ this.redirected = redirected;
13
+ this.name = 'HttpError';
14
+ }
15
+ }
16
+
17
+ /**
18
+ * Tiny fetch wrapper for making HTTP requests with TypeScript support.
19
+ * @template T - The expected response type.
20
+ * @param {string} url - The URL to request.
21
+ * @param {RequestInit} [options] - Options for the fetch request.
22
+ * @returns {Promise<T>} - A promise that resolves to the JSON response of type T.
23
+ * @throws {Error} - Throws an isError if the response is not OK.
24
+ *
25
+ * @author Pavan Kumar Jadda
26
+ * @since 1.0.3
27
+ */
28
+ export async function fetchClient<T>(url: string, options: RequestInit = {}): Promise<T> {
29
+ // Initialize headers object
30
+ const headers = new Headers(options.headers || {});
31
+
32
+ // Check if the body is FormData, if not, handle as JSON
33
+ if (options.body instanceof FormData) {
34
+ // Do not set 'Content-Type': 'application/json' for FormData
35
+ // Browser will set the correct headers for FormData
36
+ } else if (typeof options.body === 'object') {
37
+ // If there is a body, and it's an object, stringify it and set JSON header
38
+ options.body = JSON.stringify(options.body);
39
+ headers.set('Content-Type', 'application/json');
40
+ }
41
+
42
+ // Merge options with default settings
43
+ const fetchOptions = {
44
+ ...options,
45
+ headers,
46
+ };
47
+
48
+ // If there is a body, and it's an object, stringify it
49
+ if (fetchOptions.body && typeof fetchOptions.body === 'object') {
50
+ fetchOptions.body = JSON.stringify(fetchOptions.body);
51
+ }
52
+
53
+ // Make the fetch request
54
+ const response = await fetch(url, fetchOptions);
55
+
56
+ // Check if the response is OK
57
+ if (response.type === 'error') {
58
+ const errorText = await response.text();
59
+ throw new HttpError(errorText, response);
60
+ }
61
+
62
+ // Parse and return the JSON response
63
+ return (await response.json()) as Promise<T>;
64
+ }
@@ -0,0 +1,69 @@
1
+ import { fireEvent, render, screen } from '@testing-library/react';
2
+ import { CancelButton } from '../../lib/components/buttons/CancelButton';
3
+ import '@testing-library/jest-dom';
4
+ import jest from 'jest-mock';
5
+
6
+ test('renders CancelButton component', () => {
7
+ render(
8
+ <CancelButton
9
+ name={'Cancel'}
10
+ onClick={() => {
11
+ console.log('Clicked Cancel Button');
12
+ }}
13
+ />
14
+ );
15
+
16
+ const cancelButton = screen.getByRole('button', { name: 'Cancel' });
17
+ expect(cancelButton).toBeInTheDocument();
18
+ });
19
+
20
+ test('renders CancelButton with children', () => {
21
+ render(
22
+ <CancelButton
23
+ onClick={() => {
24
+ console.log('Clicked Cancel Button');
25
+ }}>
26
+ Cancel
27
+ </CancelButton>
28
+ );
29
+
30
+ const cancelButton = screen.getByRole('button', { name: 'Cancel' });
31
+ expect(cancelButton).toBeInTheDocument();
32
+ });
33
+
34
+ test('renders with custom className', () => {
35
+ render(<CancelButton name="Cancel" className="custom-class" onClick={() => {}} />);
36
+
37
+ const cancelButton = screen.getByRole('button', { name: 'Cancel' });
38
+ expect(cancelButton).toHaveClass('custom-class');
39
+ });
40
+
41
+ test('renders with custom data-cy attribute', () => {
42
+ render(<CancelButton name="Cancel" dataCy="custom-cancel-button" onClick={() => {}} />);
43
+
44
+ const cancelButton = screen.getByRole('button', { name: 'Cancel' });
45
+ expect(cancelButton).toHaveAttribute('data-cy', 'custom-cancel-button');
46
+ });
47
+
48
+ test('renders with custom startIcon', () => {
49
+ render(<CancelButton name="Cancel" startIcon={<span data-testid="custom-icon" />} onClick={() => {}} />);
50
+
51
+ const customIcon = screen.getByTestId('custom-icon');
52
+ expect(customIcon).toBeInTheDocument();
53
+ });
54
+
55
+ test('renders with custom type', () => {
56
+ render(<CancelButton name="Cancel" type="submit" onClick={() => {}} />);
57
+
58
+ const cancelButton = screen.getByRole('button', { name: 'Cancel' });
59
+ expect(cancelButton).toHaveAttribute('type', 'submit');
60
+ });
61
+
62
+ test('calls onClick when clicked', () => {
63
+ const handleClick = jest.fn();
64
+ render(<CancelButton name="Cancel" onClick={handleClick} />);
65
+
66
+ const cancelButton = screen.getByRole('button', { name: 'Cancel' });
67
+ fireEvent.click(cancelButton);
68
+ expect(handleClick).toHaveBeenCalledTimes(1);
69
+ });
@@ -0,0 +1,63 @@
1
+ import { fireEvent, render, screen } from '@testing-library/react';
2
+ import { DeleteButton } from '../../lib/components/buttons/DeleteButton';
3
+ import '@testing-library/jest-dom';
4
+ import jest from 'jest-mock';
5
+
6
+ test('renders DeleteButton component', () => {
7
+ render(
8
+ <DeleteButton
9
+ loading={false}
10
+ onClick={() => {
11
+ console.log('Clicked Delete Button');
12
+ }}
13
+ />
14
+ );
15
+
16
+ const deleteButton = screen.getByRole('button', { name: 'Delete' });
17
+ expect(deleteButton).toBeInTheDocument();
18
+ });
19
+
20
+ test('renders DeleteButton with custom label', () => {
21
+ render(
22
+ <DeleteButton
23
+ loading={false}
24
+ label="Remove"
25
+ onClick={() => {
26
+ console.log('Clicked Delete Button');
27
+ }}
28
+ />
29
+ );
30
+
31
+ const deleteButton = screen.getByRole('button', { name: 'Remove' });
32
+ expect(deleteButton).toBeInTheDocument();
33
+ });
34
+
35
+ test('renders with custom data-cy attribute', () => {
36
+ render(<DeleteButton loading={false} name="Delete" dataCy="custom-delete-button" onClick={() => {}} />);
37
+
38
+ const deleteButton = screen.getByRole('button', { name: 'Delete' });
39
+ expect(deleteButton).toHaveAttribute('data-cy', 'custom-delete-button');
40
+ });
41
+
42
+ test('renders with custom startIcon', () => {
43
+ render(<DeleteButton loading={false} name="Delete" startIcon={<span data-testid="custom-icon" />} onClick={() => {}} />);
44
+
45
+ const customIcon = screen.getByTestId('custom-icon');
46
+ expect(customIcon).toBeInTheDocument();
47
+ });
48
+
49
+ test('renders with custom type', () => {
50
+ render(<DeleteButton loading={false} name="Delete" type="submit" onClick={() => {}} />);
51
+
52
+ const deleteButton = screen.getByRole('button', { name: 'Delete' });
53
+ expect(deleteButton).toHaveAttribute('type', 'submit');
54
+ });
55
+
56
+ test('calls onClick when clicked', () => {
57
+ const handleClick = jest.fn();
58
+ render(<DeleteButton loading={false} name="Delete" onClick={handleClick} />);
59
+
60
+ const deleteButton = screen.getByRole('button', { name: 'Delete' });
61
+ fireEvent.click(deleteButton);
62
+ expect(handleClick).toHaveBeenCalledTimes(1);
63
+ });
@@ -0,0 +1,34 @@
1
+ import React from 'react';
2
+ import { fireEvent, render, screen } from '@testing-library/react';
3
+ import '@testing-library/jest-dom';
4
+ import { EditIconButton } from '../../lib/components/buttons/EditIconButton';
5
+ import jest from 'jest-mock';
6
+
7
+ describe('EditIconButton', () => {
8
+ const mockOnClick = jest.fn();
9
+
10
+ beforeEach(() => {
11
+ mockOnClick.mockClear();
12
+ });
13
+
14
+ it('renders with the correct tooltip title', () => {
15
+ render(<EditIconButton tooltipTitle="Edit Item" onClick={mockOnClick} />);
16
+ expect(screen.getByLabelText('Edit Item')).toBeInTheDocument();
17
+ });
18
+
19
+ it('calls onClick when clicked', () => {
20
+ render(<EditIconButton tooltipTitle="Edit Item" onClick={mockOnClick} />);
21
+ fireEvent.click(screen.getByLabelText('Edit Item'));
22
+ expect(mockOnClick).toHaveBeenCalledTimes(1);
23
+ });
24
+
25
+ it('uses the default color if none is provided', () => {
26
+ render(<EditIconButton tooltipTitle="Edit Item" onClick={mockOnClick} />);
27
+ expect(screen.getByLabelText('Edit Item')).toHaveClass('MuiIconButton-colorPrimary');
28
+ });
29
+
30
+ it('uses the provided color', () => {
31
+ render(<EditIconButton tooltipTitle="Edit Item" onClick={mockOnClick} color="secondary" />);
32
+ expect(screen.getByLabelText('Edit Item')).toHaveClass('MuiIconButton-colorSecondary');
33
+ });
34
+ });