pixel-react 1.0.1 → 1.0.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/.yarn/install-state.gz +0 -0
- package/lib/components/MultiSelect/MultiSelect.d.ts +1 -1
- package/lib/components/MultiSelect/MultiSelectTypes.d.ts +2 -0
- package/lib/components/MultiSelect/dropdownTypes.d.ts +2 -0
- package/lib/index.d.ts +3 -1
- package/lib/index.esm.js +512 -2327
- package/lib/index.esm.js.map +1 -1
- package/lib/index.js +512 -2328
- package/lib/index.js.map +1 -1
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/lib/utils/compareArrays/compareArrays.d.ts +11 -0
- package/lib/utils/compareArrays/compareArrays.stories.d.ts +6 -0
- package/lib/utils/compareObjects/compareObjects.d.ts +2 -0
- package/lib/utils/compareObjects/compareObjects.stories.d.ts +6 -0
- package/lib/utils/debounce/debounce.d.ts +6 -0
- package/lib/utils/debounce/debounce.stories.d.ts +6 -0
- package/lib/utils/find/findAndInsert.d.ts +7 -0
- package/lib/utils/find/findAndInsert.stories.d.ts +7 -0
- package/lib/utils/throttle/throttle.d.ts +6 -0
- package/lib/utils/throttle/throttle.stories.d.ts +6 -0
- package/package.json +3 -1
- package/rollup.config.mjs +10 -5
- package/src/StyleGuide/ColorPalette/ColorPalette.scss +4 -5
- package/src/StyleGuide/Typography/Typography.scss +4 -5
- package/src/assets/Themes/Theme.scss +7 -191
- package/src/assets/icons/filter.svg +5 -0
- package/src/components/Accordion/Accordion.scss +1 -2
- package/src/components/Button/Button.scss +1 -1
- package/src/components/Charts/DonutChart/DonutChart.scss +1 -1
- package/src/components/Charts/RadialChart/RadialChart.scss +1 -1
- package/src/components/Drawer/Drawer.scss +9 -10
- package/src/components/ExpandableMenu/ExpandableMenu.scss +1 -1
- package/src/components/Icon/Icons.scss +5 -6
- package/src/components/Icon/iconList.ts +2 -0
- package/src/components/MenuOption/MenuOption.scss +1 -1
- package/src/components/MultiSelect/Dropdown.scss +27 -16
- package/src/components/MultiSelect/Dropdown.tsx +51 -28
- package/src/components/MultiSelect/MultiSelect.scss +1 -1
- package/src/components/MultiSelect/MultiSelect.stories.tsx +9 -5
- package/src/components/MultiSelect/MultiSelect.tsx +4 -0
- package/src/components/MultiSelect/MultiSelectTypes.ts +2 -0
- package/src/components/MultiSelect/dropdownTypes.ts +2 -0
- package/src/components/RadioButton/RadioButton.scss +2 -3
- package/src/components/Search/Search.scss +1 -1
- package/src/components/Table/Table.scss +1 -1
- package/src/components/TextArea/Textarea.scss +1 -2
- package/src/components/Toggle/Toggle.scss +5 -4
- package/src/components/Tooltip/Tooltip.scss +1 -1
- package/src/index.ts +1 -4
- package/src/utils/checkEmpty/checkEmpty.stories.tsx +1 -1
- package/src/utils/checkEmpty/checkEmpty.ts +21 -7
- package/src/utils/compareArrays/compareArrays.stories.tsx +62 -0
- package/src/utils/compareArrays/compareArrays.ts +31 -0
- package/src/utils/compareObjects/compareObjects.stories.tsx +51 -0
- package/src/utils/compareObjects/compareObjects.ts +53 -0
- package/src/utils/debounce/debounce.stories.tsx +81 -0
- package/src/utils/debounce/debounce.ts +28 -0
- package/src/utils/find/findAndInsert.stories.tsx +119 -0
- package/src/utils/find/findAndInsert.ts +49 -0
- package/src/utils/throttle/throttle.stories.tsx +100 -0
- package/src/utils/throttle/throttle.ts +33 -0
- package/vite.config.js +8 -16
@@ -10,25 +10,27 @@
|
|
10
10
|
box-sizing: border-box;
|
11
11
|
margin-block: 4px;
|
12
12
|
max-height: 160px;
|
13
|
-
overflow-y: auto; // Change from scroll to auto to avoid arrow buttons
|
14
13
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
14
|
+
.ff-multiselect-options-wrapper {
|
15
|
+
max-height: 128px;
|
16
|
+
overflow-y: auto;
|
17
|
+
&::-webkit-scrollbar {
|
18
|
+
width: 4px;
|
19
|
+
height: 40px;
|
20
|
+
border-radius: 12px 0px 0px 0px;
|
21
|
+
background: var(--description-text);
|
21
22
|
|
22
|
-
|
23
|
-
|
24
|
-
|
23
|
+
&-track {
|
24
|
+
background: var(--ff-select-scroll-track-bg);
|
25
|
+
}
|
25
26
|
|
26
|
-
|
27
|
-
|
28
|
-
|
27
|
+
&-thumb {
|
28
|
+
background: var(--ff-select-scroll-thumb-color);
|
29
|
+
border-radius: 10px;
|
29
30
|
|
30
|
-
|
31
|
-
|
31
|
+
&:hover {
|
32
|
+
background: var(--ff-select-scroll-thumb-hover);
|
33
|
+
}
|
32
34
|
}
|
33
35
|
}
|
34
36
|
}
|
@@ -53,7 +55,7 @@
|
|
53
55
|
|
54
56
|
.dropdown-option-label {
|
55
57
|
@extend .fontMd;
|
56
|
-
color: var(--
|
58
|
+
color: var(--ff-select-text-color);
|
57
59
|
}
|
58
60
|
&:hover {
|
59
61
|
background-color: var(--hover-color);
|
@@ -63,4 +65,13 @@
|
|
63
65
|
&:focus {
|
64
66
|
outline: none;
|
65
67
|
}
|
68
|
+
.select-button-container {
|
69
|
+
box-sizing: border-box;
|
70
|
+
width: 100%;
|
71
|
+
border-top: 1px solid var(--slider-table-color);
|
72
|
+
height: 30px;
|
73
|
+
display: flex;
|
74
|
+
justify-content: center;
|
75
|
+
align-items: center;
|
76
|
+
}
|
66
77
|
}
|
@@ -1,9 +1,10 @@
|
|
1
|
-
import { forwardRef, useContext } from 'react';
|
1
|
+
import { forwardRef, useContext, useMemo } from 'react';
|
2
2
|
import { dropdownDefaultCSSData, DropdownProps } from './dropdownTypes';
|
3
3
|
|
4
4
|
import './Dropdown.scss';
|
5
5
|
import { ThemeContext } from '../ThemeProvider/ThemeProvider';
|
6
6
|
import classNames from 'classnames';
|
7
|
+
import Button from '../Button';
|
7
8
|
|
8
9
|
const Dropdown = forwardRef<HTMLDivElement, DropdownProps>(
|
9
10
|
(
|
@@ -13,6 +14,8 @@ const Dropdown = forwardRef<HTMLDivElement, DropdownProps>(
|
|
13
14
|
searchedKeyword = '',
|
14
15
|
dropdownPosition = {},
|
15
16
|
zIndex,
|
17
|
+
withSelectButton,
|
18
|
+
onSelect,
|
16
19
|
},
|
17
20
|
ref
|
18
21
|
) => {
|
@@ -23,19 +26,29 @@ const Dropdown = forwardRef<HTMLDivElement, DropdownProps>(
|
|
23
26
|
: [];
|
24
27
|
const { verticalMargin, optionHeight, maxDropdownHeight } =
|
25
28
|
dropdownDefaultCSSData;
|
26
|
-
const dropdownHeight = Math.min(
|
27
|
-
filteredOptions.length * optionHeight,
|
28
|
-
maxDropdownHeight
|
29
|
-
);
|
30
|
-
const isEnoughBottomSpaceAvailable =
|
31
|
-
dropdownPosition.fromBottom >= dropdownHeight + verticalMargin;
|
32
29
|
|
33
|
-
const topPosition =
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
30
|
+
const topPosition = useMemo(() => {
|
31
|
+
let calculatedDropdownHeight = Math.min(
|
32
|
+
filteredOptions.length * optionHeight,
|
33
|
+
maxDropdownHeight
|
34
|
+
);
|
35
|
+
|
36
|
+
if (filteredOptions.length < 5 && withSelectButton) {
|
37
|
+
calculatedDropdownHeight += 32;
|
38
|
+
}
|
39
|
+
|
40
|
+
const isEnoughSpaceAvailable =
|
41
|
+
dropdownPosition.fromBottom >=
|
42
|
+
calculatedDropdownHeight + verticalMargin;
|
43
|
+
const topPosition = isEnoughSpaceAvailable
|
44
|
+
? dropdownPosition.top
|
45
|
+
: dropdownPosition.top -
|
46
|
+
calculatedDropdownHeight -
|
47
|
+
dropdownPosition.selectHeight -
|
48
|
+
2 * verticalMargin;
|
49
|
+
|
50
|
+
return topPosition;
|
51
|
+
}, [filteredOptions.length, withSelectButton, dropdownPosition]);
|
39
52
|
const themeContext = useContext(ThemeContext);
|
40
53
|
const currentTheme = themeContext?.currentTheme;
|
41
54
|
return (
|
@@ -52,22 +65,32 @@ const Dropdown = forwardRef<HTMLDivElement, DropdownProps>(
|
|
52
65
|
zIndex,
|
53
66
|
}}
|
54
67
|
>
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
role="option"
|
63
|
-
className={`dropdown-option-container`}
|
64
|
-
key={info.label}
|
65
|
-
onClick={() => handleOptionChange(info, !info.isChecked)}
|
66
|
-
>
|
67
|
-
<input type="checkbox" checked={info.isChecked} readOnly />
|
68
|
-
<span className="dropdown-option-label">{info.label}</span>
|
68
|
+
<div
|
69
|
+
className="ff-multiselect-options-wrapper"
|
70
|
+
style={{ maxHeight: withSelectButton ? '128px' : '160px' }}
|
71
|
+
>
|
72
|
+
{filteredOptions.length === 0 ? (
|
73
|
+
<div className="dropdown-option-container ">
|
74
|
+
<p className="no-options">No Option</p>
|
69
75
|
</div>
|
70
|
-
)
|
76
|
+
) : (
|
77
|
+
filteredOptions.map((info) => (
|
78
|
+
<div
|
79
|
+
role="option"
|
80
|
+
className={`dropdown-option-container`}
|
81
|
+
key={info.label}
|
82
|
+
onClick={() => handleOptionChange(info, !info.isChecked)}
|
83
|
+
>
|
84
|
+
<input type="checkbox" checked={info.isChecked} readOnly />
|
85
|
+
<span className="dropdown-option-label">{info.label}</span>
|
86
|
+
</div>
|
87
|
+
))
|
88
|
+
)}
|
89
|
+
</div>
|
90
|
+
{withSelectButton && filteredOptions.length > 0 && (
|
91
|
+
<div className="select-button-container">
|
92
|
+
<Button label="Select" variant="tertiary" onClick={onSelect} />
|
93
|
+
</div>
|
71
94
|
)}
|
72
95
|
</div>
|
73
96
|
);
|
@@ -22,17 +22,21 @@ export const Default: Story = {
|
|
22
22
|
args: {
|
23
23
|
...defaultArgs,
|
24
24
|
required: true,
|
25
|
+
withSelectButton: true,
|
26
|
+
onSelect: () => {
|
27
|
+
alert('selected');
|
28
|
+
},
|
25
29
|
options: [
|
26
30
|
{ label: 'Apple', value: 'apple' },
|
27
31
|
{ label: 'Banana', value: 'banana' },
|
28
32
|
{ label: 'Cherry', value: 'cherry' },
|
29
33
|
{ label: 'Date', value: 'date' },
|
30
34
|
{ label: 'Grape', value: 'grape' },
|
31
|
-
{ label: 'Kiwi', value: 'kiwi' },
|
32
|
-
{ label: 'Mango', value: 'mango' },
|
33
|
-
{ label: 'Orange', value: 'orange' },
|
34
|
-
{ label: 'Peach', value: 'peach' },
|
35
|
-
{ label: 'Strawberry', value: 'strawberry' },
|
35
|
+
// { label: 'Kiwi', value: 'kiwi' },
|
36
|
+
// { label: 'Mango', value: 'mango' },
|
37
|
+
// { label: 'Orange', value: 'orange' },
|
38
|
+
// { label: 'Peach', value: 'peach' },
|
39
|
+
// { label: 'Strawberry', value: 'strawberry' },
|
36
40
|
],
|
37
41
|
// selectedOptions: [{ label: 'Apple', value: 'apple' }],
|
38
42
|
},
|
@@ -38,6 +38,8 @@ const MultiSelect = ({
|
|
38
38
|
required = false,
|
39
39
|
disabled = false,
|
40
40
|
errorMessage = 'Fill this field',
|
41
|
+
withSelectButton = false,
|
42
|
+
onSelect = () => {},
|
41
43
|
}: MultiSelectProps) => {
|
42
44
|
const [isOpen, setIsOpen] = useState<boolean>(false);
|
43
45
|
const [allOptions, setAllOptions] = useState(options);
|
@@ -249,6 +251,8 @@ const MultiSelect = ({
|
|
249
251
|
options={allOptions}
|
250
252
|
dropdownPosition={dropdownPosition}
|
251
253
|
zIndex={zIndex}
|
254
|
+
withSelectButton={withSelectButton}
|
255
|
+
onSelect={onSelect}
|
252
256
|
/>,
|
253
257
|
document.body
|
254
258
|
)}
|
@@ -1,5 +1,4 @@
|
|
1
|
-
@
|
2
|
-
@import '../../assets/styles/_fonts';
|
1
|
+
@use '../../assets/styles/_fonts';
|
3
2
|
|
4
3
|
@mixin circle($size, $border: none) {
|
5
4
|
width: $size;
|
@@ -16,7 +15,7 @@
|
|
16
15
|
.ff-radio {
|
17
16
|
@extend .fontSm;
|
18
17
|
position: relative;
|
19
|
-
color:
|
18
|
+
color: var(--text-color);
|
20
19
|
display: flex;
|
21
20
|
align-items: center;
|
22
21
|
cursor: pointer;
|
@@ -1,3 +1,4 @@
|
|
1
|
+
@use "sass:map";
|
1
2
|
$switch-sizes: (
|
2
3
|
small: (
|
3
4
|
button-size: 16px,
|
@@ -20,10 +21,10 @@ $switch-sizes: (
|
|
20
21
|
);
|
21
22
|
|
22
23
|
@mixin switch-size($size) {
|
23
|
-
$button-size: map
|
24
|
-
$container-width: map
|
25
|
-
$container-height: map
|
26
|
-
$translate-x: map
|
24
|
+
$button-size: map.get(map.get($switch-sizes, $size), button-size);
|
25
|
+
$container-width: map.get(map.get($switch-sizes, $size), container-width);
|
26
|
+
$container-height: map.get(map.get($switch-sizes, $size), container-height);
|
27
|
+
$translate-x: map.get(map.get($switch-sizes, $size), translate-x);
|
27
28
|
|
28
29
|
width: $container-width;
|
29
30
|
height: $container-height;
|
package/src/index.ts
CHANGED
@@ -26,7 +26,6 @@ import Typography from './components/Typography';
|
|
26
26
|
import useTheme from './hooks/useTheme';
|
27
27
|
import Form from './components/Form';
|
28
28
|
|
29
|
-
|
30
29
|
import InputWithDropdown from './components/InputWithDropdown';
|
31
30
|
import RadioButton from './components/RadioButton';
|
32
31
|
import RadioGroup from './components/RadioGroup';
|
@@ -35,7 +34,7 @@ import TableTree from './components/TableTree';
|
|
35
34
|
import Tabs from './components/Tabs';
|
36
35
|
import HighlightText from './components/HighlightText';
|
37
36
|
import Checkbox from './components/Checkbox';
|
38
|
-
import Search from './components/Search/Search'
|
37
|
+
import Search from './components/Search/Search';
|
39
38
|
|
40
39
|
// Utils imports
|
41
40
|
import { checkEmpty } from './utils/checkEmpty/checkEmpty';
|
@@ -69,11 +68,9 @@ export {
|
|
69
68
|
useTheme,
|
70
69
|
InputWithDropdown,
|
71
70
|
Table,
|
72
|
-
|
73
71
|
RadioButton,
|
74
72
|
RadioGroup,
|
75
73
|
Form,
|
76
|
-
|
77
74
|
MiniModal,
|
78
75
|
Container,
|
79
76
|
Row,
|
@@ -1,10 +1,24 @@
|
|
1
1
|
type valueType = any;
|
2
2
|
|
3
|
-
export const checkEmpty = (value: valueType) => {
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
3
|
+
export const checkEmpty = (value: valueType): boolean => {
|
4
|
+
// Check for null or undefined
|
5
|
+
if (value == null) return true;
|
6
|
+
|
7
|
+
// Check for strings
|
8
|
+
if (typeof value === 'string') return value.trim().length === 0;
|
9
|
+
|
10
|
+
// Check for arrays
|
11
|
+
if (Array.isArray(value)) return value.length === 0;
|
12
|
+
|
13
|
+
// Check for objects
|
14
|
+
if (typeof value === 'object') {
|
15
|
+
// Handle Map and Set
|
16
|
+
if (value instanceof Map || value instanceof Set) return value.size === 0;
|
17
|
+
|
18
|
+
// Handle regular objects
|
19
|
+
return Object.keys(value).length === 0;
|
20
|
+
}
|
21
|
+
|
22
|
+
// For all other types (numbers, booleans, etc.), return false as they are not "empty"
|
23
|
+
return false;
|
10
24
|
};
|
@@ -0,0 +1,62 @@
|
|
1
|
+
import { compareArrays } from './compareArrays'; // Adjust the import path as necessary
|
2
|
+
|
3
|
+
export default {
|
4
|
+
title: 'Utils/compareArrays',
|
5
|
+
component: compareArrays,
|
6
|
+
};
|
7
|
+
|
8
|
+
export const Default = () => {
|
9
|
+
const testCases = [
|
10
|
+
{ arr1: [1, 2, 3], arr2: [1, 2, 3], expected: true },
|
11
|
+
{ arr1: [1, 2, 3], arr2: [1, 2, 4], expected: false },
|
12
|
+
{ arr1: [1, { a: 1 }], arr2: [1, { a: 1 }], expected: true },
|
13
|
+
{ arr1: [1, { a: 1 }], arr2: [1, { a: 2 }], expected: false },
|
14
|
+
{ arr1: [1, 2, 3], arr2: [1, 2, 3, 4], expected: false },
|
15
|
+
{ arr1: [], arr2: [], expected: true },
|
16
|
+
{ arr1: [null, undefined], arr2: [null, undefined], expected: true },
|
17
|
+
{ arr1: [null], arr2: [undefined], expected: false },
|
18
|
+
{
|
19
|
+
arr1: [
|
20
|
+
[1, 2],
|
21
|
+
[3, 4],
|
22
|
+
],
|
23
|
+
arr2: [
|
24
|
+
[1, 2],
|
25
|
+
[3, 4],
|
26
|
+
],
|
27
|
+
expected: true,
|
28
|
+
},
|
29
|
+
{
|
30
|
+
arr1: [
|
31
|
+
[1, 2],
|
32
|
+
[3, 4],
|
33
|
+
],
|
34
|
+
arr2: [
|
35
|
+
[1, 2],
|
36
|
+
[4, 3],
|
37
|
+
],
|
38
|
+
expected: false,
|
39
|
+
},
|
40
|
+
];
|
41
|
+
|
42
|
+
return (
|
43
|
+
<div>
|
44
|
+
<h1>
|
45
|
+
<u>compareArrays(arr1, arr2)</u> - Expected / Actual
|
46
|
+
</h1>
|
47
|
+
{testCases.map(({ arr1, arr2, expected }, index) => {
|
48
|
+
const actual = compareArrays(arr1, arr2);
|
49
|
+
return (
|
50
|
+
<div key={index}>
|
51
|
+
<strong>
|
52
|
+
compareArrays({JSON.stringify(arr1)}, {JSON.stringify(arr2)}) -
|
53
|
+
</strong>
|
54
|
+
<span style={{ color: actual === expected ? 'green' : 'red' }}>
|
55
|
+
{` Expected: ${expected}, Actual: ${actual}`}
|
56
|
+
</span>
|
57
|
+
</div>
|
58
|
+
);
|
59
|
+
})}
|
60
|
+
</div>
|
61
|
+
);
|
62
|
+
};
|
@@ -0,0 +1,31 @@
|
|
1
|
+
import { compareObjects } from '../compareObjects/compareObjects';
|
2
|
+
|
3
|
+
// Define a type for any object
|
4
|
+
export type AnyObject = Record<string, unknown>;
|
5
|
+
|
6
|
+
/**
|
7
|
+
* Compare two arrays for equality.
|
8
|
+
* This function checks if both arrays contain the same elements in the same order,
|
9
|
+
* including nested structures.
|
10
|
+
*
|
11
|
+
* @param arr1 - The first array to compare.
|
12
|
+
* @param arr2 - The second array to compare.
|
13
|
+
* @returns - A boolean indicating if the arrays are equal.
|
14
|
+
*/
|
15
|
+
export const compareArrays = (arr1: unknown[], arr2: unknown[]): boolean => {
|
16
|
+
// Check if both are arrays
|
17
|
+
if (!Array.isArray(arr1) || !Array.isArray(arr2)) return false;
|
18
|
+
|
19
|
+
// Check if lengths are different
|
20
|
+
if (arr1.length !== arr2.length) return false;
|
21
|
+
|
22
|
+
// Compare each element
|
23
|
+
return arr1.every((element, index) => {
|
24
|
+
const otherElement = arr2[index];
|
25
|
+
// Recursively compare objects or arrays, or check for strict equality
|
26
|
+
return compareObjects(
|
27
|
+
element as AnyObject | null,
|
28
|
+
otherElement as AnyObject | null
|
29
|
+
);
|
30
|
+
});
|
31
|
+
};
|
@@ -0,0 +1,51 @@
|
|
1
|
+
import { compareObjects } from './compareObjects'; // Adjust the import path as necessary
|
2
|
+
|
3
|
+
export default {
|
4
|
+
title: 'Utils/compareObjects',
|
5
|
+
component: compareObjects,
|
6
|
+
};
|
7
|
+
|
8
|
+
export const Default = () => {
|
9
|
+
const testCases = [
|
10
|
+
{
|
11
|
+
obj1: { a: 1, b: [2, { c: 3 }] },
|
12
|
+
obj2: { a: 1, b: [2, { c: 3 }] },
|
13
|
+
expected: true,
|
14
|
+
},
|
15
|
+
{
|
16
|
+
obj1: { a: 1, b: [2, { c: 3 }] },
|
17
|
+
obj2: { a: 1, b: [3, 2] },
|
18
|
+
expected: false,
|
19
|
+
},
|
20
|
+
{
|
21
|
+
obj1: { a: 1, b: { c: 2 } },
|
22
|
+
obj2: { a: 1, b: { c: 2 } },
|
23
|
+
expected: true,
|
24
|
+
},
|
25
|
+
{
|
26
|
+
obj1: { a: 1, b: { c: 2 } },
|
27
|
+
obj2: { a: 1, b: { c: 3 } },
|
28
|
+
expected: false,
|
29
|
+
},
|
30
|
+
{ obj1: null, obj2: null, expected: true },
|
31
|
+
{ obj1: { a: null }, obj2: { a: null }, expected: true },
|
32
|
+
{ obj1: { a: 1 }, obj2: { a: null }, expected: false },
|
33
|
+
{ obj1: {}, obj2: {}, expected: true },
|
34
|
+
{ obj1: { a: 0 }, obj2: { a: 0 }, expected: true },
|
35
|
+
{ obj1: { a: 0 }, obj2: { a: 1 }, expected: false },
|
36
|
+
];
|
37
|
+
|
38
|
+
return (
|
39
|
+
<div>
|
40
|
+
<h1>
|
41
|
+
<u>compareObjects(obj1, obj2)</u> - true / false
|
42
|
+
</h1>
|
43
|
+
{testCases.map(({ obj1, obj2, expected }, index) => (
|
44
|
+
<div key={index}>
|
45
|
+
compareObjects({JSON.stringify(obj1)}, {JSON.stringify(obj2)}) -
|
46
|
+
{compareObjects(obj1, obj2) === expected ? ' True' : ' False'}
|
47
|
+
</div>
|
48
|
+
))}
|
49
|
+
</div>
|
50
|
+
);
|
51
|
+
};
|
@@ -0,0 +1,53 @@
|
|
1
|
+
// Define a type for any object
|
2
|
+
export type AnyObject = Record<string, unknown>;
|
3
|
+
|
4
|
+
export const compareObjects = (
|
5
|
+
obj1: AnyObject | null,
|
6
|
+
obj2: AnyObject | null
|
7
|
+
): boolean => {
|
8
|
+
// Check if both are strictly equal (handles primitive types and same reference)
|
9
|
+
if (obj1 === obj2) return true;
|
10
|
+
|
11
|
+
// Check if either is null or not an object
|
12
|
+
if (
|
13
|
+
obj1 == null ||
|
14
|
+
obj2 == null ||
|
15
|
+
typeof obj1 !== 'object' ||
|
16
|
+
typeof obj2 !== 'object'
|
17
|
+
) {
|
18
|
+
return false;
|
19
|
+
}
|
20
|
+
|
21
|
+
// Handle array comparison
|
22
|
+
const isArray1 = Array.isArray(obj1);
|
23
|
+
const isArray2 = Array.isArray(obj2);
|
24
|
+
if (isArray1 !== isArray2) return false; // One is an array, the other is not
|
25
|
+
|
26
|
+
// Create arrays of keys for both objects
|
27
|
+
const keys1 = isArray1 ? obj1 : Object.keys(obj1);
|
28
|
+
const keys2 = isArray2 ? obj2 : Object.keys(obj2);
|
29
|
+
|
30
|
+
// Check if the number of keys is different
|
31
|
+
if (keys1.length !== keys2.length) return false;
|
32
|
+
|
33
|
+
// Create a Set for keys2 for O(1) lookups (only for objects)
|
34
|
+
if (!isArray1) {
|
35
|
+
const keysSet2 = new Set<string>(keys2 as string[]);
|
36
|
+
|
37
|
+
// Check each key and value
|
38
|
+
return keys1.every((key) => {
|
39
|
+
return (
|
40
|
+
keysSet2.has(key) &&
|
41
|
+
compareObjects(
|
42
|
+
obj1[key] as AnyObject | null,
|
43
|
+
obj2[key] as AnyObject | null
|
44
|
+
)
|
45
|
+
);
|
46
|
+
});
|
47
|
+
} else {
|
48
|
+
// If arrays, compare elements directly
|
49
|
+
return keys1.every((item, index) =>
|
50
|
+
compareObjects(item as AnyObject | null, keys2[index] as AnyObject | null)
|
51
|
+
);
|
52
|
+
}
|
53
|
+
};
|
@@ -0,0 +1,81 @@
|
|
1
|
+
import { debounce } from './debounce'; // Adjust the import path as necessary
|
2
|
+
|
3
|
+
export default {
|
4
|
+
title: 'Utils/debounce',
|
5
|
+
component: debounce,
|
6
|
+
};
|
7
|
+
|
8
|
+
export const Default = () => {
|
9
|
+
const testCases = [
|
10
|
+
{
|
11
|
+
description: 'Basic function call',
|
12
|
+
expectedOutput: 'Function called! (after delay)',
|
13
|
+
delay: 300,
|
14
|
+
setup: () => {
|
15
|
+
const debouncedFunc = debounce(
|
16
|
+
() => console.log('Function called!'),
|
17
|
+
300
|
18
|
+
);
|
19
|
+
debouncedFunc(); // Call the debounced function
|
20
|
+
},
|
21
|
+
code: `const debouncedFunc = debounce(() => console.log('Function called!'), 300);\ndebouncedFunc();`,
|
22
|
+
},
|
23
|
+
{
|
24
|
+
description: 'Rapid calls within delay',
|
25
|
+
expectedOutput: 'Debounced call! (only once after delay)',
|
26
|
+
delay: 500,
|
27
|
+
setup: () => {
|
28
|
+
const debouncedFunc = debounce(
|
29
|
+
() => console.log('Debounced call!'),
|
30
|
+
500
|
31
|
+
);
|
32
|
+
for (let i = 0; i < 5; i++) {
|
33
|
+
debouncedFunc(); // Call the debounced function multiple times
|
34
|
+
}
|
35
|
+
},
|
36
|
+
code: `const debouncedFunc = debounce(() => console.log('Debounced call!'), 500);\nfor (let i = 0; i < 5; i++) {\n debouncedFunc();\n}`,
|
37
|
+
},
|
38
|
+
{
|
39
|
+
description: 'Cancel debounced function',
|
40
|
+
expectedOutput: 'Function should not be called if canceled',
|
41
|
+
delay: 200,
|
42
|
+
setup: () => {
|
43
|
+
const debouncedFunc = debounce(
|
44
|
+
() => console.log('Should not be called!'),
|
45
|
+
200
|
46
|
+
);
|
47
|
+
debouncedFunc(); // Call the debounced function
|
48
|
+
debouncedFunc.cancel(); // Cancel the function
|
49
|
+
},
|
50
|
+
code: `const debouncedFunc = debounce(() => console.log('Should not be called!'), 200);\ndebouncedFunc();\ndebouncedFunc.cancel();`,
|
51
|
+
},
|
52
|
+
];
|
53
|
+
|
54
|
+
return (
|
55
|
+
<div>
|
56
|
+
<h1>
|
57
|
+
<u>debounce(function, delay)</u> - Demonstrating debounce functionality
|
58
|
+
</h1>
|
59
|
+
{testCases.map(
|
60
|
+
({ description, expectedOutput, delay, setup, code }, index) => (
|
61
|
+
<div key={index}>
|
62
|
+
<h3>{description}</h3>
|
63
|
+
<button
|
64
|
+
onClick={() => {
|
65
|
+
setup(); // Run the setup for the test case
|
66
|
+
setTimeout(() => {
|
67
|
+
console.log(expectedOutput);
|
68
|
+
}, delay + 100); // Wait a bit longer than the delay to check output
|
69
|
+
}}
|
70
|
+
>
|
71
|
+
Run Test
|
72
|
+
</button>
|
73
|
+
<pre>
|
74
|
+
<code>{code}</code>
|
75
|
+
</pre>
|
76
|
+
</div>
|
77
|
+
)
|
78
|
+
)}
|
79
|
+
</div>
|
80
|
+
);
|
81
|
+
};
|