dhre-component-lib 0.0.9 → 0.1.0
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 +3 -2
- package/src/__mocks__/styleMock.js +1 -0
- package/src/components/Avatar/Avatar.test.tsx +53 -0
- package/src/components/Avatar/Avatar.tsx +28 -0
- package/src/components/Avatar/index.ts +1 -0
- package/src/components/Badge/Badge.module.scss +14 -0
- package/src/components/Badge/Badge.test.tsx +59 -0
- package/src/components/Badge/Badge.tsx +25 -0
- package/src/components/Badge/index.ts +1 -0
- package/src/components/BreadCrumb/BreadCrumb.test.tsx +90 -0
- package/src/components/BreadCrumb/BreadCrumb.tsx +45 -0
- package/src/components/BreadCrumb/Breadcrumb.module.scss +20 -0
- package/src/components/BreadCrumb/index.ts +1 -0
- package/src/components/Button/Button.module.scss +66 -0
- package/src/components/Button/Button.test.tsx +49 -0
- package/src/components/Button/Button.tsx +29 -0
- package/src/components/Button/index.ts +1 -0
- package/src/components/Checkbox/Checkbox.test.tsx +93 -0
- package/src/components/Checkbox/Checkbox.tsx +35 -0
- package/src/components/Checkbox/index.ts +1 -0
- package/src/components/CircularProgress/CircularProgress.module.scss +19 -0
- package/src/components/CircularProgress/CircularProgress.test.tsx +39 -0
- package/src/components/CircularProgress/CircularProgress.tsx +37 -0
- package/src/components/CircularProgress/index.ts +1 -0
- package/src/components/Divider/Divider.test.tsx +44 -0
- package/src/components/Divider/Divider.tsx +24 -0
- package/src/components/Divider/index.ts +1 -0
- package/src/components/Enum.ts +19 -0
- package/src/components/InputTextField/InputTextField.test.tsx +118 -0
- package/src/components/InputTextField/InputTextField.tsx +48 -0
- package/src/components/InputTextField/index.ts +1 -0
- package/src/components/Link/Link.test.tsx +55 -0
- package/src/components/Link/Link.tsx +33 -0
- package/src/components/Link/index.ts +1 -0
- package/src/components/Map/Directions.tsx +36 -0
- package/src/components/Map/GoogleMap.module.scss +5 -0
- package/src/components/Map/GoogleMap.tsx +186 -0
- package/src/components/Map/GoogleMapsLoader.tsx +12 -0
- package/src/components/Map/index.ts +2 -0
- package/src/components/Modal/Modal.module.scss +26 -0
- package/src/components/Modal/Modal.test.tsx +74 -0
- package/src/components/Modal/Modal.tsx +39 -0
- package/src/components/Modal/index.ts +1 -0
- package/src/components/Notification/Notification.module.scss +20 -0
- package/src/components/Notification/Notification.test.tsx +53 -0
- package/src/components/Notification/Notification.tsx +42 -0
- package/src/components/Notification/index.ts +1 -0
- package/src/components/OtpInput/OtpInput.module.scss +49 -0
- package/src/components/OtpInput/OtpInput.test.tsx +53 -0
- package/src/components/OtpInput/OtpInput.tsx +137 -0
- package/src/components/OtpInput/index.ts +1 -0
- package/src/components/PdfView/PdfView.module.scss +69 -0
- package/src/components/PdfView/PdfView.test.tsx +52 -0
- package/src/components/PdfView/PdfView.tsx +116 -0
- package/src/components/PdfView/index.ts +1 -0
- package/src/components/Progress/Progress.test.tsx +43 -0
- package/src/components/Progress/Progress.tsx +35 -0
- package/src/components/Progress/index.ts +1 -0
- package/src/components/RadioButton/RadioButton.test.tsx +56 -0
- package/src/components/RadioButton/RadioButton.tsx +43 -0
- package/src/components/RadioButton/index.ts +1 -0
- package/src/components/Switch/Switch.test.tsx +83 -0
- package/src/components/Switch/Switch.tsx +38 -0
- package/src/components/Switch/index.ts +1 -0
- package/src/components/Tag/Tag.css +14 -0
- package/src/components/Tag/Tag.test.tsx +61 -0
- package/src/components/Tag/Tag.tsx +19 -0
- package/src/components/Tag/index.ts +1 -0
- package/src/components/Tooltip/Tooltip.module.scss +37 -0
- package/src/components/Tooltip/Tooltip.test.tsx +68 -0
- package/src/components/Tooltip/Tooltip.tsx +38 -0
- package/src/components/Tooltip/index.ts +1 -0
- package/src/components/index.ts +15 -0
- package/src/index.ts +1 -0
- package/src/theme/Typography/typography.scss +117 -0
- package/src/theme/colors/colors.scss +22 -0
- package/src/theme/colors.ts +3 -0
- package/src/typings.d.ts +1 -0
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import React, { useEffect } from "react";
|
|
2
|
+
import styles from './PdfView.module.scss';
|
|
3
|
+
|
|
4
|
+
export interface PdfViewerProps {
|
|
5
|
+
content: string;
|
|
6
|
+
contentType?: string;
|
|
7
|
+
buttonText?: string;
|
|
8
|
+
loadingText?: string;
|
|
9
|
+
errorText?: string;
|
|
10
|
+
cleanUpDelay?: number;
|
|
11
|
+
buttonVariant?: "text" | "outlined" | "contained";
|
|
12
|
+
buttonColor?: "inherit" | "primary" | "secondary" | "error" | "info" | "success" | "warning";
|
|
13
|
+
buttonSize?: "small" | "medium" | "large";
|
|
14
|
+
showLoadingSpinner?: boolean;
|
|
15
|
+
spinnerSize?: number;
|
|
16
|
+
spinnerColor?: "inherit" | "primary" | "secondary";
|
|
17
|
+
className?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const PdfView: React.FC<PdfViewerProps> = ({
|
|
21
|
+
content,
|
|
22
|
+
contentType = "application/pdf",
|
|
23
|
+
buttonText = "View PDF",
|
|
24
|
+
loadingText = "Loading...",
|
|
25
|
+
errorText = "Failed to load PDF",
|
|
26
|
+
cleanUpDelay = 1000,
|
|
27
|
+
buttonVariant = "contained",
|
|
28
|
+
buttonColor = "primary",
|
|
29
|
+
buttonSize = "medium",
|
|
30
|
+
showLoadingSpinner = false,
|
|
31
|
+
spinnerSize = 24,
|
|
32
|
+
spinnerColor = "primary",
|
|
33
|
+
className = "",
|
|
34
|
+
}) => {
|
|
35
|
+
const [isLoading, setIsLoading] = React.useState(false);
|
|
36
|
+
const [isError, setIsError] = React.useState(false);
|
|
37
|
+
const [errorMsg, setErrorMsg] = React.useState(errorText);
|
|
38
|
+
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
if (btoa(atob(content)) !== content) {
|
|
41
|
+
setIsError(true);
|
|
42
|
+
}
|
|
43
|
+
}, [content]);
|
|
44
|
+
|
|
45
|
+
const base64ToBlob = (content: string, contentType: string = "application/pdf"): Blob | null => {
|
|
46
|
+
try {
|
|
47
|
+
const byteCharacters = atob(content);
|
|
48
|
+
const byteArray = new Uint8Array(byteCharacters.length);
|
|
49
|
+
|
|
50
|
+
for (let i = 0; i < byteCharacters.length; i++) {
|
|
51
|
+
byteArray[i] = byteCharacters.charCodeAt(i);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return new Blob([byteArray], { type: contentType });
|
|
55
|
+
} catch (error) {
|
|
56
|
+
setIsError(true);
|
|
57
|
+
setErrorMsg("Invalid base64 string:");
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const handleViewPdf = async () => {
|
|
63
|
+
setIsLoading(true);
|
|
64
|
+
setIsError(false);
|
|
65
|
+
const blob = base64ToBlob(content, contentType);
|
|
66
|
+
if (blob) {
|
|
67
|
+
const url = URL.createObjectURL(blob);
|
|
68
|
+
|
|
69
|
+
window.open(url, "_blank");
|
|
70
|
+
|
|
71
|
+
setTimeout(() => {
|
|
72
|
+
URL.revokeObjectURL(url);
|
|
73
|
+
}, cleanUpDelay);
|
|
74
|
+
setIsLoading(false);
|
|
75
|
+
} else {
|
|
76
|
+
setIsLoading(false);
|
|
77
|
+
setIsError(true);
|
|
78
|
+
setErrorMsg("Failed to create Blob from base64 string.");
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const buttonClass = `
|
|
83
|
+
${styles.button}
|
|
84
|
+
${styles[`button${buttonVariant.charAt(0).toUpperCase() + buttonVariant.slice(1)}`]}
|
|
85
|
+
${styles[`button${buttonColor.charAt(0).toUpperCase() + buttonColor.slice(1)}`]}
|
|
86
|
+
${styles[`button${buttonSize.charAt(0).toUpperCase() + buttonSize.slice(1)}`]}
|
|
87
|
+
`;
|
|
88
|
+
|
|
89
|
+
const spinnerClass = `${styles.spinner} ${styles.spinnerInner}`;
|
|
90
|
+
|
|
91
|
+
return (
|
|
92
|
+
<div className={`${styles.container} ${className}`}>
|
|
93
|
+
{isLoading && showLoadingSpinner && (
|
|
94
|
+
<div
|
|
95
|
+
data-testid="spinnertest"
|
|
96
|
+
className={spinnerClass}
|
|
97
|
+
style={{ width: spinnerSize, height: spinnerSize, borderColor: spinnerColor }}
|
|
98
|
+
>
|
|
99
|
+
<div className={styles.spinnerInner}></div>
|
|
100
|
+
</div>
|
|
101
|
+
)}
|
|
102
|
+
{isError && <p className={styles.errorText}>{errorMsg}</p>}
|
|
103
|
+
{!isLoading && !isError && (
|
|
104
|
+
<button
|
|
105
|
+
onClick={handleViewPdf}
|
|
106
|
+
className={buttonClass}
|
|
107
|
+
>
|
|
108
|
+
{buttonText}
|
|
109
|
+
</button>
|
|
110
|
+
)}
|
|
111
|
+
{isLoading && !showLoadingSpinner && <p>{loadingText}</p>}
|
|
112
|
+
</div>
|
|
113
|
+
);
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
export default PdfView;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "./PdfView";
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, screen } from '@testing-library/react';
|
|
3
|
+
import '@testing-library/jest-dom';
|
|
4
|
+
import Progress from './Progress';
|
|
5
|
+
|
|
6
|
+
describe('Progress Component', () => {
|
|
7
|
+
it('renders the progress bar with correct percentage width', () => {
|
|
8
|
+
render(<Progress value={50} max={100} barClassName="progress-bar" />);
|
|
9
|
+
const progressBar = screen.getByTestId('progress-bar');
|
|
10
|
+
expect(progressBar).toHaveStyle('width: 50%');
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('renders the label when provided', () => {
|
|
14
|
+
render(<Progress value={30} max={100} label="Loading" labelClassName="label-class" />);
|
|
15
|
+
expect(screen.getByText('Loading')).toHaveClass('label-class');
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('does not render the label when not provided', () => {
|
|
19
|
+
render(<Progress value={30} max={100} />);
|
|
20
|
+
expect(screen.queryByText('Loading')).not.toBeInTheDocument();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
it('handles zero values correctly', () => {
|
|
26
|
+
render(<Progress value={0} max={100} barClassName="progress-bar" />);
|
|
27
|
+
const progressBar = screen.getByTestId('progress-bar');
|
|
28
|
+
expect(progressBar).toHaveStyle('width: 0%');
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('handles max values correctly', () => {
|
|
32
|
+
render(<Progress value={100} max={100} barClassName="progress-bar" />);
|
|
33
|
+
const progressBar = screen.getByTestId('progress-bar');
|
|
34
|
+
expect(progressBar).toHaveStyle('width: 100%');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('handles values greater than max', () => {
|
|
38
|
+
render(<Progress value={150} max={100} barClassName="progress-bar" />);
|
|
39
|
+
const progressBar = screen.getByTestId('progress-bar');
|
|
40
|
+
expect(progressBar).toHaveStyle('width: 150%'); // Matches the component's behavior
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
})
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
export interface ProgressProps {
|
|
4
|
+
value: number;
|
|
5
|
+
max: number;
|
|
6
|
+
className?: string;
|
|
7
|
+
barContainerClassName?: string;
|
|
8
|
+
barClassName?: string;
|
|
9
|
+
labelClassName?: string;
|
|
10
|
+
label?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const Progress: React.FC<ProgressProps> = ({
|
|
14
|
+
value,
|
|
15
|
+
max,
|
|
16
|
+
className = '',
|
|
17
|
+
barContainerClassName = '',
|
|
18
|
+
barClassName = '',
|
|
19
|
+
labelClassName = '',
|
|
20
|
+
label,
|
|
21
|
+
...rest
|
|
22
|
+
}) => {
|
|
23
|
+
const percentage = (value / max) * 100;
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<div className={className} {...rest}>
|
|
27
|
+
{label && <div className={labelClassName}>{label}</div>}
|
|
28
|
+
<div className={barContainerClassName}>
|
|
29
|
+
<div data-testid="progress-bar" className={barClassName} style={{ width: `${percentage}%` }} />
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export default Progress;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "./Progress";
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, screen, fireEvent } from '@testing-library/react';
|
|
3
|
+
import '@testing-library/jest-dom';
|
|
4
|
+
import CustomRadioButton from './RadioButton';
|
|
5
|
+
|
|
6
|
+
describe('CustomRadioButton', () => {
|
|
7
|
+
it('should render with given props', () => {
|
|
8
|
+
render(
|
|
9
|
+
<CustomRadioButton
|
|
10
|
+
name="testName"
|
|
11
|
+
value="testValue"
|
|
12
|
+
checked={true}
|
|
13
|
+
onChange={() => {}}
|
|
14
|
+
className="custom-class"
|
|
15
|
+
inputClassName="input-class"
|
|
16
|
+
labelClassName="label-class"
|
|
17
|
+
id="radio1"
|
|
18
|
+
/>
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
const radioInput = screen.getByRole('radio');
|
|
22
|
+
const label = screen.getByText('testValue'); // Use text to find the label
|
|
23
|
+
|
|
24
|
+
// Assert that the radio input is rendered correctly
|
|
25
|
+
expect(radioInput).toBeInTheDocument();
|
|
26
|
+
expect(radioInput).toHaveAttribute('name', 'testName');
|
|
27
|
+
expect(radioInput).toHaveAttribute('value', 'testValue');
|
|
28
|
+
expect(radioInput).toBeChecked();
|
|
29
|
+
expect(radioInput).toHaveClass('input-class');
|
|
30
|
+
|
|
31
|
+
// Assert that the label is rendered correctly
|
|
32
|
+
expect(label).toBeInTheDocument();
|
|
33
|
+
|
|
34
|
+
// Ensure the label has the correct class by finding the label element directly
|
|
35
|
+
const labelElement = document.querySelector(`label[for="radio1"]`);
|
|
36
|
+
expect(labelElement).toHaveClass('label-class');
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('should call onChange handler when clicked', () => {
|
|
40
|
+
const handleChange = jest.fn();
|
|
41
|
+
|
|
42
|
+
render(
|
|
43
|
+
<CustomRadioButton
|
|
44
|
+
name="testName"
|
|
45
|
+
value="testValue"
|
|
46
|
+
onChange={handleChange}
|
|
47
|
+
id="radio1"
|
|
48
|
+
/>
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
const radioInput = screen.getByRole('radio');
|
|
52
|
+
fireEvent.click(radioInput);
|
|
53
|
+
|
|
54
|
+
expect(handleChange).toHaveBeenCalled();
|
|
55
|
+
});
|
|
56
|
+
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
interface RadioButtonProps {
|
|
4
|
+
name: string;
|
|
5
|
+
value: string;
|
|
6
|
+
checked?: boolean;
|
|
7
|
+
onChange?: React.ChangeEventHandler<HTMLInputElement>;
|
|
8
|
+
className?: string;
|
|
9
|
+
inputClassName?: string;
|
|
10
|
+
labelClassName?: string;
|
|
11
|
+
id?: string;
|
|
12
|
+
[key: string]: any;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const CustomRadioButton: React.FC<RadioButtonProps> = ({
|
|
16
|
+
name,
|
|
17
|
+
value,
|
|
18
|
+
checked = false,
|
|
19
|
+
onChange,
|
|
20
|
+
className = '',
|
|
21
|
+
inputClassName = '',
|
|
22
|
+
labelClassName = '',
|
|
23
|
+
id,
|
|
24
|
+
...rest
|
|
25
|
+
}) => {
|
|
26
|
+
return (
|
|
27
|
+
<div className={className}>
|
|
28
|
+
<input
|
|
29
|
+
type="radio"
|
|
30
|
+
name={name}
|
|
31
|
+
value={value}
|
|
32
|
+
checked={checked}
|
|
33
|
+
onChange={onChange}
|
|
34
|
+
id={id}
|
|
35
|
+
className={inputClassName}
|
|
36
|
+
{...rest}
|
|
37
|
+
/>
|
|
38
|
+
<label htmlFor={id} className={labelClassName}>{value}</label>
|
|
39
|
+
</div>
|
|
40
|
+
);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export default CustomRadioButton;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "./RadioButton";
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { render, screen, fireEvent } from '@testing-library/react';
|
|
3
|
+
import '@testing-library/jest-dom';
|
|
4
|
+
import Switch from './Switch';
|
|
5
|
+
|
|
6
|
+
describe('Switch Component', () => {
|
|
7
|
+
it('renders with basic props', () => {
|
|
8
|
+
const { container } = render(<Switch checked={true} handleChange={() => {}} switchClassName="switch-class" />);
|
|
9
|
+
|
|
10
|
+
const switchInput = screen.getByRole('checkbox');
|
|
11
|
+
const switchToggle = container.querySelector('.switch-class');
|
|
12
|
+
|
|
13
|
+
expect(switchInput).toBeInTheDocument();
|
|
14
|
+
expect(switchInput).toBeChecked();
|
|
15
|
+
expect(switchToggle).toBeInTheDocument();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('renders with a label', () => {
|
|
19
|
+
render(
|
|
20
|
+
<Switch
|
|
21
|
+
checked={true}
|
|
22
|
+
handleChange={() => {}}
|
|
23
|
+
switchClassName="switch-class"
|
|
24
|
+
label="Toggle Switch"
|
|
25
|
+
labelClassName="label-class"
|
|
26
|
+
/>
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
const label = screen.getByText('Toggle Switch');
|
|
30
|
+
const switchInput = screen.getByRole('checkbox');
|
|
31
|
+
|
|
32
|
+
expect(label).toBeInTheDocument();
|
|
33
|
+
expect(label).toHaveClass('label-class');
|
|
34
|
+
expect(switchInput).toBeChecked();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('calls handleChange handler when clicked', () => {
|
|
38
|
+
// Simulate a component that manages its own state
|
|
39
|
+
const TestComponent = () => {
|
|
40
|
+
const [checked, setChecked] = useState(false);
|
|
41
|
+
return (
|
|
42
|
+
<Switch
|
|
43
|
+
checked={checked}
|
|
44
|
+
handleChange={() => setChecked(!checked)}
|
|
45
|
+
switchClassName="switch-class"
|
|
46
|
+
/>
|
|
47
|
+
);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
render(<TestComponent />);
|
|
51
|
+
|
|
52
|
+
const switchInput = screen.getByRole('checkbox');
|
|
53
|
+
expect(switchInput).not.toBeChecked();
|
|
54
|
+
|
|
55
|
+
// Simulate the click event
|
|
56
|
+
fireEvent.click(switchInput);
|
|
57
|
+
|
|
58
|
+
// Check if the switchInput is now checked
|
|
59
|
+
expect(switchInput).toBeChecked();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('applies custom classes', () => {
|
|
63
|
+
const { container } = render(
|
|
64
|
+
<Switch
|
|
65
|
+
checked={true}
|
|
66
|
+
handleChange={() => {}}
|
|
67
|
+
switchClassName="switch-class"
|
|
68
|
+
containerClassName="container-class"
|
|
69
|
+
toggleClassName="toggle-class"
|
|
70
|
+
label="Toggle Switch"
|
|
71
|
+
labelClassName="label-class"
|
|
72
|
+
/>
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
const containerDiv = container.querySelector('.container-class');
|
|
76
|
+
const switchToggle = container.querySelector('.switch-class');
|
|
77
|
+
const label = screen.getByText('Toggle Switch');
|
|
78
|
+
|
|
79
|
+
expect(containerDiv).toHaveClass('container-class');
|
|
80
|
+
expect(switchToggle).toHaveClass('switch-class');
|
|
81
|
+
expect(label).toHaveClass('label-class');
|
|
82
|
+
});
|
|
83
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import './Switch.css';
|
|
3
|
+
|
|
4
|
+
export interface SwitchProps {
|
|
5
|
+
checked: boolean;
|
|
6
|
+
handleChange: () => void;
|
|
7
|
+
switchClassName: string;
|
|
8
|
+
label?: string;
|
|
9
|
+
labelClassName?: string;
|
|
10
|
+
containerClassName?: string;
|
|
11
|
+
toggleClassName?:string,
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const Switch: React.FC<SwitchProps> = ({
|
|
15
|
+
checked,
|
|
16
|
+
handleChange,
|
|
17
|
+
switchClassName,
|
|
18
|
+
label,
|
|
19
|
+
labelClassName,
|
|
20
|
+
containerClassName,
|
|
21
|
+
toggleClassName
|
|
22
|
+
}) => {
|
|
23
|
+
return (
|
|
24
|
+
<div className={containerClassName}>
|
|
25
|
+
{label && <span className={labelClassName}>{label}</span>}
|
|
26
|
+
<label className={toggleClassName}>
|
|
27
|
+
<input
|
|
28
|
+
type="checkbox"
|
|
29
|
+
checked={checked}
|
|
30
|
+
onChange={handleChange}
|
|
31
|
+
/>
|
|
32
|
+
<span className={switchClassName}></span>
|
|
33
|
+
</label>
|
|
34
|
+
</div>
|
|
35
|
+
);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export default Switch;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "./Switch";
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, screen, fireEvent } from '@testing-library/react';
|
|
3
|
+
import '@testing-library/jest-dom';
|
|
4
|
+
import Tag, { TagProps } from './Tag';
|
|
5
|
+
|
|
6
|
+
describe('Tag Component', () => {
|
|
7
|
+
let handleClick: jest.Mock;
|
|
8
|
+
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
handleClick = jest.fn(); // Reset the mock function before each test
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
// Function to generate default props for each test
|
|
14
|
+
const getDefaultProps = (): TagProps => ({
|
|
15
|
+
content: 'Sample Tag',
|
|
16
|
+
tagClassName: 'tag-class',
|
|
17
|
+
handleClick,
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('renders the tag with the correct content', () => {
|
|
21
|
+
render(<Tag {...getDefaultProps()} />);
|
|
22
|
+
|
|
23
|
+
const tagElement = screen.getByText('Sample Tag');
|
|
24
|
+
|
|
25
|
+
expect(tagElement).toBeInTheDocument();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('applies the correct className to the tag', () => {
|
|
29
|
+
render(<Tag {...getDefaultProps()} />);
|
|
30
|
+
|
|
31
|
+
const tagElement = screen.getByText('Sample Tag');
|
|
32
|
+
|
|
33
|
+
expect(tagElement).toHaveClass('tag-class');
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('calls the handleClick function when the tag is clicked', () => {
|
|
37
|
+
render(<Tag {...getDefaultProps()} />);
|
|
38
|
+
|
|
39
|
+
const tagElement = screen.getByText('Sample Tag');
|
|
40
|
+
|
|
41
|
+
fireEvent.click(tagElement);
|
|
42
|
+
|
|
43
|
+
expect(handleClick).toHaveBeenCalledTimes(1);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('does not call handleClick when the tag is not clicked', () => {
|
|
47
|
+
render(<Tag {...getDefaultProps()} />);
|
|
48
|
+
|
|
49
|
+
expect(handleClick).not.toHaveBeenCalled();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('renders correctly with different content', () => {
|
|
53
|
+
const newContent = 'New Tag Content';
|
|
54
|
+
render(<Tag {...getDefaultProps()} content={newContent} />);
|
|
55
|
+
|
|
56
|
+
const tagElement = screen.getByText(newContent);
|
|
57
|
+
|
|
58
|
+
expect(tagElement).toBeInTheDocument();
|
|
59
|
+
expect(tagElement).toHaveTextContent(newContent);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import styles from "./Tag.module.scss";
|
|
3
|
+
|
|
4
|
+
export interface TagProps {
|
|
5
|
+
content: string | number;
|
|
6
|
+
tagClassName?: string; // Optional prop for custom class name
|
|
7
|
+
handleClick?: () => void;
|
|
8
|
+
style?: React.CSSProperties; // Inline styles as props
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const Tag: React.FC<TagProps> = ({ content, tagClassName = styles.customTagClass, handleClick, style }) => {
|
|
12
|
+
return (
|
|
13
|
+
<button onClick={handleClick} style={style} className={tagClassName}>
|
|
14
|
+
{content}
|
|
15
|
+
</button>
|
|
16
|
+
);
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export default Tag;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "./Tag";
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
.tooltipWrapper {
|
|
2
|
+
display: inline-block;
|
|
3
|
+
position: relative;
|
|
4
|
+
margin-right: 20px;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.customTooltipClass {
|
|
8
|
+
position: absolute;
|
|
9
|
+
bottom: 125%; /* Position above the element */
|
|
10
|
+
left: 50%;
|
|
11
|
+
transform: translateX(-50%);
|
|
12
|
+
background-color: #333;
|
|
13
|
+
color: #fff;
|
|
14
|
+
padding: 5px 10px;
|
|
15
|
+
border-radius: 4px;
|
|
16
|
+
font-size: 14px;
|
|
17
|
+
white-space: nowrap;
|
|
18
|
+
visibility: visible;
|
|
19
|
+
opacity: 1;
|
|
20
|
+
transition: opacity 0.2s ease-in-out;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.customTooltipClass::after {
|
|
24
|
+
content: "";
|
|
25
|
+
position: absolute;
|
|
26
|
+
top: 100%;
|
|
27
|
+
left: 50%;
|
|
28
|
+
transform: translateX(-50%);
|
|
29
|
+
border-width: 5px;
|
|
30
|
+
border-style: solid;
|
|
31
|
+
border-color: #333 transparent transparent transparent;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.hoverText {
|
|
35
|
+
color: blue;
|
|
36
|
+
cursor: pointer;
|
|
37
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { render, screen, fireEvent } from "@testing-library/react";
|
|
3
|
+
import "@testing-library/jest-dom";
|
|
4
|
+
import Tooltip, { TooltipProps } from "./Tooltip";
|
|
5
|
+
|
|
6
|
+
describe("Tooltip Component", () => {
|
|
7
|
+
const defaultProps: TooltipProps = {
|
|
8
|
+
content: "Tooltip content",
|
|
9
|
+
tooltipClassName: "tooltip-class",
|
|
10
|
+
children: <button>Hover me</button>,
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
it("renders the children correctly", () => {
|
|
14
|
+
render(<Tooltip {...defaultProps} />);
|
|
15
|
+
|
|
16
|
+
const childElement = screen.getByText("Hover me");
|
|
17
|
+
|
|
18
|
+
expect(childElement).toBeInTheDocument();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("does not show tooltip content by default", () => {
|
|
22
|
+
render(<Tooltip {...defaultProps} />);
|
|
23
|
+
|
|
24
|
+
const tooltipElement = screen.queryByText(defaultProps.content);
|
|
25
|
+
|
|
26
|
+
expect(tooltipElement).not.toBeInTheDocument();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("shows tooltip content on mouse enter and hides it on mouse leave", () => {
|
|
30
|
+
render(<Tooltip {...defaultProps} />);
|
|
31
|
+
|
|
32
|
+
const childElement = screen.getByText("Hover me");
|
|
33
|
+
|
|
34
|
+
// Trigger mouse enter
|
|
35
|
+
fireEvent.mouseEnter(childElement);
|
|
36
|
+
let tooltipElement = screen.queryByText(defaultProps.content);
|
|
37
|
+
expect(tooltipElement).toBeInTheDocument();
|
|
38
|
+
expect(tooltipElement).toHaveClass(defaultProps.tooltipClassName);
|
|
39
|
+
|
|
40
|
+
// Trigger mouse leave
|
|
41
|
+
fireEvent.mouseLeave(childElement);
|
|
42
|
+
tooltipElement = screen.queryByText(defaultProps.content);
|
|
43
|
+
|
|
44
|
+
// Ensure tooltipElement is null if it was not found
|
|
45
|
+
expect(tooltipElement).toBeNull();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("applies the correct container className", () => {
|
|
49
|
+
const customClassName = "custom-container-class";
|
|
50
|
+
render(<Tooltip {...defaultProps} containerClassName={customClassName} />);
|
|
51
|
+
|
|
52
|
+
const containerElement = screen.getByText("Hover me").parentElement;
|
|
53
|
+
|
|
54
|
+
expect(containerElement).toHaveClass(customClassName);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("handles tooltip visibility correctly", () => {
|
|
58
|
+
render(<Tooltip {...defaultProps} />);
|
|
59
|
+
|
|
60
|
+
const childElement = screen.getByText("Hover me");
|
|
61
|
+
|
|
62
|
+
fireEvent.mouseEnter(childElement);
|
|
63
|
+
|
|
64
|
+
const tooltipElement = screen.queryByText(defaultProps.content);
|
|
65
|
+
|
|
66
|
+
expect(tooltipElement).toBeInTheDocument();
|
|
67
|
+
});
|
|
68
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import React, { useState } from "react";
|
|
2
|
+
import styles from './Tooltip.module.scss';
|
|
3
|
+
|
|
4
|
+
export interface TooltipProps {
|
|
5
|
+
content: string;
|
|
6
|
+
tooltipClassName: string;
|
|
7
|
+
children: React.ReactNode;
|
|
8
|
+
containerClassName?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const Tooltip: React.FC<TooltipProps> = ({
|
|
12
|
+
content,
|
|
13
|
+
tooltipClassName,
|
|
14
|
+
children,
|
|
15
|
+
containerClassName = "",
|
|
16
|
+
}) => {
|
|
17
|
+
const [visible, setVisible] = useState(false);
|
|
18
|
+
|
|
19
|
+
const showTooltip = () => setVisible(true);
|
|
20
|
+
const hideTooltip = () => setVisible(false);
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<div
|
|
24
|
+
className={`${styles.tooltipWrapper} ${containerClassName}`}
|
|
25
|
+
onMouseEnter={showTooltip}
|
|
26
|
+
onMouseLeave={hideTooltip}
|
|
27
|
+
>
|
|
28
|
+
{children}
|
|
29
|
+
{visible && (
|
|
30
|
+
<div className={`${styles.customTooltipClass} ${tooltipClassName}`}>
|
|
31
|
+
{content}
|
|
32
|
+
</div>
|
|
33
|
+
)}
|
|
34
|
+
</div>
|
|
35
|
+
);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export default Tooltip;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "./Tooltip";
|