@sonardigital/carbon-ui 1.1.83 → 1.1.86
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 +1 -1
- package/src/components/form/fields/FormBox.tsx +13 -11
- package/src/components/layout/multisteps/hooks/useMultisteps.ts +3 -0
- package/src/components/layout/multisteps/provider/MultistepsContext.ts +4 -0
- package/src/theme/constants/components.ts +14 -1
- package/src/utils/maths/maths.test.ts +122 -0
- package/src/utils/maths/maths.ts +62 -0
package/package.json
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Card, Stack } from '@mui/material';
|
|
1
|
+
import { Card, Grow, Stack } from '@mui/material';
|
|
2
2
|
import { Controller } from 'react-hook-form';
|
|
3
3
|
import CheckCircleIcon from '@mui/icons-material/CheckCircle';
|
|
4
4
|
|
|
@@ -21,16 +21,18 @@ export function FormBox({
|
|
|
21
21
|
render={({ field }) => (
|
|
22
22
|
<Stack sx={{ position: 'relative' }}>
|
|
23
23
|
{value === field.value && (
|
|
24
|
-
<
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
24
|
+
<Grow in={true} timeout={250}>
|
|
25
|
+
<CheckCircleIcon
|
|
26
|
+
sx={{
|
|
27
|
+
width: 19,
|
|
28
|
+
height: 19,
|
|
29
|
+
position: 'absolute',
|
|
30
|
+
top: 10,
|
|
31
|
+
right: 10,
|
|
32
|
+
color: `${color}.main`,
|
|
33
|
+
}}
|
|
34
|
+
/>
|
|
35
|
+
</Grow>
|
|
34
36
|
)}
|
|
35
37
|
<Card
|
|
36
38
|
// eslint-disable-next-line
|
|
@@ -2,6 +2,7 @@ import { useEffect, useState } from 'react';
|
|
|
2
2
|
import { useLocation, useNavigate } from 'react-router';
|
|
3
3
|
import { useMultistepsProps } from '../provider/MultistepsProvider';
|
|
4
4
|
import { MultistepsContextType } from '../provider/MultistepsContext';
|
|
5
|
+
import { maths } from '../../../../utils/maths/maths';
|
|
5
6
|
|
|
6
7
|
export const useMultisteps = ({
|
|
7
8
|
steps,
|
|
@@ -52,6 +53,8 @@ export const useMultisteps = ({
|
|
|
52
53
|
return {
|
|
53
54
|
steps,
|
|
54
55
|
currentStepIndex: index,
|
|
56
|
+
currentStep: steps[index - 1] || null,
|
|
57
|
+
percentage: maths.formatNumber(maths.percentage(index, steps.length)),
|
|
55
58
|
hover,
|
|
56
59
|
setHover,
|
|
57
60
|
goBack,
|
|
@@ -4,6 +4,8 @@ import { Step } from '../models';
|
|
|
4
4
|
export interface MultistepsContextType {
|
|
5
5
|
steps: Step[];
|
|
6
6
|
currentStepIndex: number;
|
|
7
|
+
currentStep: Step | null;
|
|
8
|
+
percentage: string;
|
|
7
9
|
hover: number | null;
|
|
8
10
|
setHover: (index: number | null) => void;
|
|
9
11
|
goNext: () => void;
|
|
@@ -15,6 +17,8 @@ export const MultistepsContext: Context<MultistepsContextType> =
|
|
|
15
17
|
createContext<MultistepsContextType>({
|
|
16
18
|
steps: [],
|
|
17
19
|
currentStepIndex: 1,
|
|
20
|
+
currentStep: null,
|
|
21
|
+
percentage: '0.00',
|
|
18
22
|
hover: null,
|
|
19
23
|
setHover: () => {},
|
|
20
24
|
goBack: () => {},
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Theme } from '@emotion/react';
|
|
2
|
-
import { Components, CssVarsTheme } from '@mui/material';
|
|
2
|
+
import { Components, CssVarsTheme, linearProgressClasses } from '@mui/material';
|
|
3
3
|
import { palette } from './palette';
|
|
4
4
|
|
|
5
5
|
const defaultButtonStyle = {
|
|
@@ -222,6 +222,19 @@ export const components: Components<
|
|
|
222
222
|
},
|
|
223
223
|
},
|
|
224
224
|
},
|
|
225
|
+
MuiLinearProgress: {
|
|
226
|
+
defaultProps: {
|
|
227
|
+
sx: {
|
|
228
|
+
[`& .${linearProgressClasses.bar}`]: {
|
|
229
|
+
borderRadius: 5,
|
|
230
|
+
},
|
|
231
|
+
width: '100%',
|
|
232
|
+
borderRadius: 100,
|
|
233
|
+
height: 8,
|
|
234
|
+
bgcolor: 'grey.200',
|
|
235
|
+
},
|
|
236
|
+
},
|
|
237
|
+
},
|
|
225
238
|
MuiTextField: {
|
|
226
239
|
defaultProps: {
|
|
227
240
|
size: 'medium',
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { maths } from './maths';
|
|
2
|
+
|
|
3
|
+
describe('maths.parseNumber', () => {
|
|
4
|
+
it('returns a number unchanged', () => {
|
|
5
|
+
expect(maths.parseNumber(42)).toBe(42);
|
|
6
|
+
expect(maths.parseNumber(3.14159)).toBe(3.14159);
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it('parses a plain string number', () => {
|
|
10
|
+
expect(maths.parseNumber('42')).toBe(42);
|
|
11
|
+
expect(maths.parseNumber('3.14')).toBe(3.14);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('handles Italian decimal format (comma as decimal separator)', () => {
|
|
15
|
+
expect(maths.parseNumber('1,5')).toBe(1.5);
|
|
16
|
+
expect(maths.parseNumber('99,99')).toBe(99.99);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('handles thousands separator (comma + dot present)', () => {
|
|
20
|
+
// "1,000.50" → comma treated as thousands separator → 1000.5
|
|
21
|
+
expect(maths.parseNumber('1,000.50')).toBe(1000.5);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('returns 0 for NaN values', () => {
|
|
25
|
+
expect(maths.parseNumber('abc')).toBe(0);
|
|
26
|
+
expect(maths.parseNumber('')).toBe(0);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('respects the decimals parameter', () => {
|
|
30
|
+
expect(maths.parseNumber(1.123456789, 2)).toBe(1.12);
|
|
31
|
+
expect(maths.parseNumber(1.123456789, 0)).toBe(1);
|
|
32
|
+
// Note: 1.005 is stored as 1.00499... in IEEE 754, so toFixed(2) = "1.00"
|
|
33
|
+
expect(maths.parseNumber(1.456, 2)).toBe(1.46);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('defaults to 5 decimal places', () => {
|
|
37
|
+
expect(maths.parseNumber(1.123456789)).toBe(1.12346);
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe('maths.percentage', () => {
|
|
42
|
+
it('calculates the correct percentage', () => {
|
|
43
|
+
expect(maths.percentage(50, 200)).toBe(25);
|
|
44
|
+
expect(maths.percentage(1, 3)).toBe(maths.parseNumber((1 * 100) / 3));
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('returns 0 when value is 0', () => {
|
|
48
|
+
expect(maths.percentage(0, 100)).toBe(0);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('returns 0 when total is 0 (no division by zero)', () => {
|
|
52
|
+
expect(maths.percentage(50, 0)).toBe(0);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('returns 0 when either argument is undefined', () => {
|
|
56
|
+
expect(maths.percentage(void 0, 100)).toBe(0);
|
|
57
|
+
expect(maths.percentage(50, void 0)).toBe(0);
|
|
58
|
+
expect(maths.percentage(void 0, void 0)).toBe(0);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('accepts string arguments', () => {
|
|
62
|
+
expect(maths.percentage('50', '200')).toBe(25);
|
|
63
|
+
expect(maths.percentage('1,5', '3')).toBe(50); // 1.5 / 3 * 100
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('returns 0 for negative values', () => {
|
|
67
|
+
expect(maths.percentage(-10, 100)).toBe(0);
|
|
68
|
+
expect(maths.percentage(10, -100)).toBe(0);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
describe('maths.average', () => {
|
|
73
|
+
it('calculates the average of numbers', () => {
|
|
74
|
+
expect(maths.average([10, 20, 30])).toBe(20);
|
|
75
|
+
expect(maths.average([1, 2, 3, 4])).toBe(2.5);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('returns 0 for an empty array', () => {
|
|
79
|
+
expect(maths.average([])).toBe(0);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('handles a single value', () => {
|
|
83
|
+
expect(maths.average([42])).toBe(42);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('handles string numbers in the array', () => {
|
|
87
|
+
expect(maths.average(['10', '20', '30'])).toBe(20);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('handles Italian decimal format strings', () => {
|
|
91
|
+
expect(maths.average(['1,5', '2,5'])).toBe(2);
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
describe('maths.formatNumber', () => {
|
|
96
|
+
it('formats an integer with no decimals by default', () => {
|
|
97
|
+
expect(maths.formatNumber(1234)).toBe('1234');
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('formats with the specified number of decimal places', () => {
|
|
101
|
+
expect(maths.formatNumber(1.5, 2)).toBe('1,50');
|
|
102
|
+
expect(maths.formatNumber(1.5, 3)).toBe('1,500');
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('uses the specified separator', () => {
|
|
106
|
+
expect(maths.formatNumber(1.5, 2, '.')).toBe('1.50');
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('returns "0" for NaN or Infinity', () => {
|
|
110
|
+
expect(maths.formatNumber(NaN)).toBe('0');
|
|
111
|
+
expect(maths.formatNumber(Infinity)).toBe('0');
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('parses Italian comma-as-decimal string input', () => {
|
|
115
|
+
expect(maths.formatNumber('1,5', 2)).toBe('1,50');
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('handles zero', () => {
|
|
119
|
+
expect(maths.formatNumber(0)).toBe('0');
|
|
120
|
+
expect(maths.formatNumber(0, 2)).toBe('0,00');
|
|
121
|
+
});
|
|
122
|
+
});
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
export type Decimals = 0 | 1 | 2 | 3 | 4 | 5;
|
|
2
|
+
|
|
3
|
+
class MathUtils {
|
|
4
|
+
percentage = (value: string | number | undefined, total: string | number | undefined): number => {
|
|
5
|
+
value = this.parseNumber(value?.toString() ?? 0);
|
|
6
|
+
total = this.parseNumber(total?.toString() ?? 0);
|
|
7
|
+
return value <= 0 || total <= 0 ? 0 : this.parseNumber((value * 100) / total);
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
parseNumber = (value: string | number, decimals: Decimals = 5): number => {
|
|
11
|
+
let numericValue: number;
|
|
12
|
+
|
|
13
|
+
if (typeof value === 'string') {
|
|
14
|
+
if (value.includes(',') && !value.includes('.')) {
|
|
15
|
+
numericValue = parseFloat(value.replace(',', '.'));
|
|
16
|
+
} else {
|
|
17
|
+
numericValue = parseFloat(value.replace(/,/g, ''));
|
|
18
|
+
}
|
|
19
|
+
} else {
|
|
20
|
+
numericValue = value;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (isNaN(numericValue) || !isFinite(numericValue)) {
|
|
24
|
+
return 0;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return parseFloat(numericValue?.toFixed(decimals));
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
average = (values: (string | number)[]): number => {
|
|
31
|
+
let total = 0;
|
|
32
|
+
values.forEach((value) => {
|
|
33
|
+
total += this.parseNumber(value ?? 0);
|
|
34
|
+
});
|
|
35
|
+
return values?.length > 0 ? total / values.length : 0;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
formatNumber = (value: string | number, decimals: Decimals = 0, separator = ','): string => {
|
|
39
|
+
let numericValue: number;
|
|
40
|
+
if (typeof value === 'string') {
|
|
41
|
+
if (value.includes(',') && !value.includes('.')) {
|
|
42
|
+
numericValue = parseFloat(value.replace(',', '.'));
|
|
43
|
+
} else {
|
|
44
|
+
numericValue = parseFloat(value.replace(',', ''));
|
|
45
|
+
}
|
|
46
|
+
} else {
|
|
47
|
+
numericValue = value;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (isNaN(numericValue) || !isFinite(numericValue)) {
|
|
51
|
+
return '0';
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const fixedValue = numericValue.toFixed(decimals);
|
|
55
|
+
|
|
56
|
+
const [integerPart, decimalPart] = fixedValue.split('.');
|
|
57
|
+
|
|
58
|
+
return decimals > 0 ? `${integerPart + separator + decimalPart}` : integerPart;
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export const maths = new MathUtils();
|