@scality/core-ui 0.165.0 → 0.166.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/dist/components/barchartv2/Barchart.component.d.ts +19 -0
- package/dist/components/barchartv2/Barchart.component.d.ts.map +1 -1
- package/dist/components/barchartv2/Barchart.component.js +31 -5
- package/dist/components/barchartv2/ChartTooltip.d.ts +18 -0
- package/dist/components/barchartv2/ChartTooltip.d.ts.map +1 -0
- package/dist/components/barchartv2/ChartTooltip.js +31 -0
- package/dist/components/barchartv2/utils.d.ts +0 -7
- package/dist/components/barchartv2/utils.d.ts.map +1 -1
- package/dist/components/barchartv2/utils.js +1 -29
- package/dist/components/chartlegend/ChartLegend.d.ts +9 -0
- package/dist/components/chartlegend/ChartLegend.d.ts.map +1 -1
- package/dist/components/chartlegend/ChartLegend.js +38 -9
- package/dist/components/chartlegend/ChartLegendWrapper.d.ts +4 -0
- package/dist/components/chartlegend/ChartLegendWrapper.d.ts.map +1 -1
- package/dist/components/chartlegend/ChartLegendWrapper.js +23 -2
- package/dist/components/constants.d.ts +2 -0
- package/dist/components/constants.d.ts.map +1 -1
- package/dist/components/constants.js +6 -0
- package/dist/components/constrainedtext/Constrainedtext.component.d.ts +3 -1
- package/dist/components/constrainedtext/Constrainedtext.component.d.ts.map +1 -1
- package/dist/components/constrainedtext/Constrainedtext.component.js +2 -2
- package/dist/components/date/FormattedDateTime.d.ts +2 -1
- package/dist/components/date/FormattedDateTime.d.ts.map +1 -1
- package/dist/components/date/FormattedDateTime.js +10 -0
- package/dist/components/linetimeseriechart/linetimeseriechart.component.d.ts +3 -1
- package/dist/components/linetimeseriechart/linetimeseriechart.component.d.ts.map +1 -1
- package/dist/components/linetimeseriechart/linetimeseriechart.component.js +5 -7
- package/dist/components/text/Text.component.js +1 -1
- package/dist/components/toast/Toast.component.d.ts.map +1 -1
- package/dist/components/toast/Toast.component.js +24 -11
- package/dist/next.d.ts +1 -0
- package/dist/next.d.ts.map +1 -1
- package/dist/next.js +1 -0
- package/package.json +1 -2
- package/src/lib/components/barchartv2/Barchart.component.test.tsx +99 -1
- package/src/lib/components/barchartv2/Barchart.component.tsx +39 -11
- package/src/lib/components/barchartv2/ChartTooltip.tsx +76 -0
- package/src/lib/components/barchartv2/utils.ts +2 -33
- package/src/lib/components/chartlegend/ChartLegend.test.tsx +218 -0
- package/src/lib/components/chartlegend/ChartLegend.tsx +42 -8
- package/src/lib/components/chartlegend/ChartLegendWrapper.tsx +75 -29
- package/src/lib/components/constants.ts +11 -0
- package/src/lib/components/constrainedtext/Constrainedtext.component.tsx +5 -2
- package/src/lib/components/date/FormattedDateTime.tsx +15 -1
- package/src/lib/components/linetimeseriechart/linetimeseriechart.component.tsx +8 -6
- package/src/lib/components/text/Text.component.tsx +1 -1
- package/src/lib/components/toast/Toast.component.tsx +27 -19
- package/src/lib/next.ts +1 -0
- package/stories/constrainedtext.stories.tsx +4 -1
- package/stories/linetimeseriechart.stories.tsx +30 -25
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Toast.component.d.ts","sourceRoot":"","sources":["../../../src/lib/components/toast/Toast.component.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"Toast.component.d.ts","sourceRoot":"","sources":["../../../src/lib/components/toast/Toast.component.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAU,MAAM,OAAO,CAAC;AAM1C,OAAO,EAAE,aAAa,EAAkB,MAAM,wBAAwB,CAAC;AAKvE,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,OAAO,GAAG,SAAS,GAAG,MAAM,CAAC;AAEnE,MAAM,MAAM,UAAU,GAAG;IACvB,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,SAAS,CAAC;IACnB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,QAAQ,CAAC,EAAE,aAAa,CAAC;IACzB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACvB,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;IACrC,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;CAC7B,CAAC;AAEF,eAAO,MAAM,qBAAqB,WAAY,MAAM,WAYnD,CAAC;AAsFF,iBAAS,KAAK,CAAC,EACb,IAAI,EACJ,OAAO,EACP,OAAO,EACP,QAAsB,EACtB,MAAe,EACf,WAAkB,EAClB,QAAe,EACf,IAAsC,EACtC,KAAqB,EACrB,eAAuB,EACvB,KAAK,GACN,EAAE,UAAU,kDAmDZ;AAED,OAAO,EAAE,KAAK,EAAE,CAAC"}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { motion } from 'framer-motion';
|
|
3
2
|
import { useRef } from 'react';
|
|
4
3
|
import { useTheme } from 'styled-components';
|
|
5
4
|
import { Box } from '../box/Box';
|
|
@@ -75,6 +74,29 @@ const ContentContainer = styled.div `
|
|
|
75
74
|
padding: 0px 16px;
|
|
76
75
|
position: relative;
|
|
77
76
|
`;
|
|
77
|
+
const FadingToast = styled.div `
|
|
78
|
+
align-items: flex-end;
|
|
79
|
+
background-color: ${props => props.theme.backgroundLevel1};
|
|
80
|
+
border: 1px solid ${props => props.theme.border};
|
|
81
|
+
box-shadow: 0px 4px 10px 4px #000;
|
|
82
|
+
display: flex;
|
|
83
|
+
border-radius: 4px;
|
|
84
|
+
position: relative;
|
|
85
|
+
|
|
86
|
+
@keyframes toastEnter {
|
|
87
|
+
from {
|
|
88
|
+
opacity: 0;
|
|
89
|
+
transform: translateY(-20px);
|
|
90
|
+
}
|
|
91
|
+
to {
|
|
92
|
+
opacity: 1;
|
|
93
|
+
transform: translateY(0);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
`;
|
|
97
|
+
const ToastEnter = styled(FadingToast) `
|
|
98
|
+
animation: toastEnter 0.3s ease forwards;
|
|
99
|
+
`;
|
|
78
100
|
function Toast({ open, message, onClose, position = 'top-right', status = 'info', autoDismiss = true, duration = 5000, icon = _jsx(DefaultIcon, { status: status }), width = DEFAULT_WIDTH, withProgressBar = false, style, }) {
|
|
79
101
|
const ref = useRef(null);
|
|
80
102
|
const { params } = useToastParameters({
|
|
@@ -85,7 +107,6 @@ function Toast({ open, message, onClose, position = 'top-right', status = 'info'
|
|
|
85
107
|
const positionStyle = positionOutput[position];
|
|
86
108
|
const bgColor = useGetBackgroundColor(status);
|
|
87
109
|
const rgbBgColor = useGetRgbBackgroundColor(status);
|
|
88
|
-
const theme = useTheme();
|
|
89
110
|
if (!open) {
|
|
90
111
|
return null;
|
|
91
112
|
}
|
|
@@ -93,14 +114,6 @@ function Toast({ open, message, onClose, position = 'top-right', status = 'info'
|
|
|
93
114
|
position: 'fixed',
|
|
94
115
|
...(style || positionStyle),
|
|
95
116
|
width,
|
|
96
|
-
}, children: [_jsxs(
|
|
97
|
-
alignItems: 'flex-end',
|
|
98
|
-
backgroundColor: theme.backgroundLevel1,
|
|
99
|
-
border: `1px solid ${theme.border}`,
|
|
100
|
-
boxShadow: '0px 4px 10px 4px #000',
|
|
101
|
-
display: 'flex',
|
|
102
|
-
borderRadius: '4px',
|
|
103
|
-
position: 'relative',
|
|
104
|
-
}, children: [_jsx(IconContainer, { bgColor: rgbBgColor, children: icon }), _jsx(ContentContainer, { children: _jsx(BasicText, { children: message }) }), _jsx(Box, { display: "flex", alignItems: "center", alignSelf: "stretch", children: _jsx(Button, { icon: _jsx(Icon, { name: "Close", size: "lg", color: "textSecondary" }), onClick: params === null || params === void 0 ? void 0 : params.onClose, "aria-label": "Close", tooltip: { overlay: 'Close', placement: 'top' } }) })] }, "toast"), withProgressBar && (_jsx(DurationBasedProgressBar, { duration: autoDismiss ? duration : null, color: bgColor }))] }));
|
|
117
|
+
}, children: [_jsxs(ToastEnter, { children: [_jsx(IconContainer, { bgColor: rgbBgColor, children: icon }), _jsx(ContentContainer, { children: _jsx(BasicText, { children: message }) }), _jsx(Box, { display: "flex", alignItems: "center", alignSelf: "stretch", children: _jsx(Button, { icon: _jsx(Icon, { name: "Close", size: "lg", color: "textSecondary" }), onClick: params === null || params === void 0 ? void 0 : params.onClose, "aria-label": "Close", tooltip: { overlay: 'Close', placement: 'top' } }) })] }), withProgressBar && (_jsx(DurationBasedProgressBar, { duration: autoDismiss ? duration : null, color: bgColor }))] }));
|
|
105
118
|
}
|
|
106
119
|
export { Toast };
|
package/dist/next.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ export { Box } from './components/box/Box';
|
|
|
14
14
|
export { Input } from './components/inputv2/inputv2';
|
|
15
15
|
export { Accordion } from './components/accordion/Accordion.component';
|
|
16
16
|
export { Barchart, BarchartSortFn, BarchartTooltipFn, } from './components/barchartv2/Barchart.component';
|
|
17
|
+
export { ChartTooltip } from './components/barchartv2/ChartTooltip';
|
|
17
18
|
export { ChartLegendWrapper } from './components/chartlegend/ChartLegendWrapper';
|
|
18
19
|
export { ChartLegend } from './components/chartlegend/ChartLegend';
|
|
19
20
|
export { LineTimeSerieChart } from './components/linetimeseriechart/linetimeseriechart.component';
|
package/dist/next.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"next.d.ts","sourceRoot":"","sources":["../src/lib/next.ts"],"names":[],"mappings":"AAAA,OAAO,2CAA2C,CAAC;AACnD,OAAO,aAAa,CAAC;AACrB,OAAO,EAAE,MAAM,EAAE,MAAM,0CAA0C,CAAC;AAClE,OAAO,EAAE,UAAU,EAAE,MAAM,4CAA4C,CAAC;AACxE,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,sCAAsC,CAAC;AACjE,OAAO,EAAE,KAAK,EAAE,MAAM,wCAAwC,CAAC;AAC/D,OAAO,EAAE,iBAAiB,EAAE,MAAM,4DAA4D,CAAC;AAC/F,OAAO,EACL,uBAAuB,EACvB,kBAAkB,GACnB,MAAM,uDAAuD,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,6CAA6C,CAAC;AACjF,OAAO,EAAE,MAAM,EAAE,MAAM,0CAA0C,CAAC;AAClE,OAAO,EAAE,cAAc,EAAE,MAAM,wDAAwD,CAAC;AACxF,OAAO,EAAE,mBAAmB,EAAE,MAAM,sDAAsD,CAAC;AAC3F,OAAO,EAAE,GAAG,EAAE,MAAM,sBAAsB,CAAC;AAC3C,OAAO,EAAE,KAAK,EAAE,MAAM,8BAA8B,CAAC;AACrD,OAAO,EAAE,SAAS,EAAE,MAAM,4CAA4C,CAAC;AACvE,OAAO,EACL,QAAQ,EACR,cAAc,EACd,iBAAiB,GAClB,MAAM,4CAA4C,CAAC;AACpD,OAAO,EAAE,kBAAkB,EAAE,MAAM,6CAA6C,CAAC;AACjF,OAAO,EAAE,WAAW,EAAE,MAAM,sCAAsC,CAAC;AACnE,OAAO,EAAE,kBAAkB,EAAE,MAAM,8DAA8D,CAAC"}
|
|
1
|
+
{"version":3,"file":"next.d.ts","sourceRoot":"","sources":["../src/lib/next.ts"],"names":[],"mappings":"AAAA,OAAO,2CAA2C,CAAC;AACnD,OAAO,aAAa,CAAC;AACrB,OAAO,EAAE,MAAM,EAAE,MAAM,0CAA0C,CAAC;AAClE,OAAO,EAAE,UAAU,EAAE,MAAM,4CAA4C,CAAC;AACxE,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,sCAAsC,CAAC;AACjE,OAAO,EAAE,KAAK,EAAE,MAAM,wCAAwC,CAAC;AAC/D,OAAO,EAAE,iBAAiB,EAAE,MAAM,4DAA4D,CAAC;AAC/F,OAAO,EACL,uBAAuB,EACvB,kBAAkB,GACnB,MAAM,uDAAuD,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,6CAA6C,CAAC;AACjF,OAAO,EAAE,MAAM,EAAE,MAAM,0CAA0C,CAAC;AAClE,OAAO,EAAE,cAAc,EAAE,MAAM,wDAAwD,CAAC;AACxF,OAAO,EAAE,mBAAmB,EAAE,MAAM,sDAAsD,CAAC;AAC3F,OAAO,EAAE,GAAG,EAAE,MAAM,sBAAsB,CAAC;AAC3C,OAAO,EAAE,KAAK,EAAE,MAAM,8BAA8B,CAAC;AACrD,OAAO,EAAE,SAAS,EAAE,MAAM,4CAA4C,CAAC;AACvE,OAAO,EACL,QAAQ,EACR,cAAc,EACd,iBAAiB,GAClB,MAAM,4CAA4C,CAAC;AACpD,OAAO,EAAE,YAAY,EAAE,MAAM,sCAAsC,CAAC;AACpE,OAAO,EAAE,kBAAkB,EAAE,MAAM,6CAA6C,CAAC;AACjF,OAAO,EAAE,WAAW,EAAE,MAAM,sCAAsC,CAAC;AACnE,OAAO,EAAE,kBAAkB,EAAE,MAAM,8DAA8D,CAAC"}
|
package/dist/next.js
CHANGED
|
@@ -14,6 +14,7 @@ export { Box } from './components/box/Box';
|
|
|
14
14
|
export { Input } from './components/inputv2/inputv2';
|
|
15
15
|
export { Accordion } from './components/accordion/Accordion.component';
|
|
16
16
|
export { Barchart, } from './components/barchartv2/Barchart.component';
|
|
17
|
+
export { ChartTooltip } from './components/barchartv2/ChartTooltip';
|
|
17
18
|
export { ChartLegendWrapper } from './components/chartlegend/ChartLegendWrapper';
|
|
18
19
|
export { ChartLegend } from './components/chartlegend/ChartLegend';
|
|
19
20
|
export { LineTimeSerieChart } from './components/linetimeseriechart/linetimeseriechart.component';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scality/core-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.166.0",
|
|
4
4
|
"description": "Scality common React component library",
|
|
5
5
|
"author": "Scality Engineering",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE",
|
|
@@ -112,7 +112,6 @@
|
|
|
112
112
|
"@js-temporal/polyfill": "^0.4.4",
|
|
113
113
|
"@storybook/preview-api": "^8.3.6",
|
|
114
114
|
"downshift": "^7.0.5",
|
|
115
|
-
"framer-motion": "^4.1.17",
|
|
116
115
|
"polished": "3.4.1",
|
|
117
116
|
"pretty-bytes": "^5.6.0",
|
|
118
117
|
"react-debounce-input": "3.2.2",
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { render, screen, waitFor } from '@testing-library/react';
|
|
2
2
|
import { getWrapper } from '../../testUtils';
|
|
3
|
-
import { Barchart } from './Barchart.component';
|
|
3
|
+
import { Barchart, CustomTick, formatDate } from './Barchart.component';
|
|
4
4
|
import { ChartLegendWrapper } from '../chartlegend/ChartLegendWrapper';
|
|
5
|
+
import React from 'react';
|
|
5
6
|
|
|
6
7
|
const ONE_DAY_IN_MILLISECONDS = 24 * 60 * 60 * 1000;
|
|
7
8
|
const ONE_HOUR_IN_MILLISECONDS = 60 * 60 * 1000;
|
|
@@ -384,4 +385,101 @@ describe('Barchart', () => {
|
|
|
384
385
|
expect(screen.getByText('Test Right Title')).toBeInTheDocument();
|
|
385
386
|
expect(screen.getByLabelText('Info')).toBeInTheDocument();
|
|
386
387
|
});
|
|
388
|
+
describe('formatDate', () => {
|
|
389
|
+
it('should render the CustomTick component with over a day interval', () => {
|
|
390
|
+
const { Wrapper } = getWrapper();
|
|
391
|
+
render(
|
|
392
|
+
<Wrapper>
|
|
393
|
+
<CustomTick
|
|
394
|
+
type={{
|
|
395
|
+
type: 'time',
|
|
396
|
+
timeRange: {
|
|
397
|
+
startDate: new Date('2024-07-05'),
|
|
398
|
+
endDate: new Date('2024-07-07'),
|
|
399
|
+
interval: 2 * ONE_DAY_IN_MILLISECONDS,
|
|
400
|
+
},
|
|
401
|
+
}}
|
|
402
|
+
x={100}
|
|
403
|
+
y={100}
|
|
404
|
+
payload={{ value: new Date('2024-07-05T10:00:00').getTime() }}
|
|
405
|
+
visibleTicksCount={10}
|
|
406
|
+
width={100}
|
|
407
|
+
/>
|
|
408
|
+
</Wrapper>,
|
|
409
|
+
);
|
|
410
|
+
expect(screen.getByText('Fri05Jul 10:00')).toBeInTheDocument();
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
it('should render the CustomTick component with day format', () => {
|
|
414
|
+
const { Wrapper } = getWrapper();
|
|
415
|
+
render(
|
|
416
|
+
<Wrapper>
|
|
417
|
+
<CustomTick
|
|
418
|
+
type={{
|
|
419
|
+
type: 'time',
|
|
420
|
+
timeRange: {
|
|
421
|
+
startDate: new Date('2024-07-05'),
|
|
422
|
+
endDate: new Date('2024-07-07'),
|
|
423
|
+
interval: ONE_DAY_IN_MILLISECONDS,
|
|
424
|
+
},
|
|
425
|
+
}}
|
|
426
|
+
x={100}
|
|
427
|
+
y={100}
|
|
428
|
+
payload={{ value: new Date('2024-07-05T10:00:00').getTime() }}
|
|
429
|
+
visibleTicksCount={10}
|
|
430
|
+
width={100}
|
|
431
|
+
/>
|
|
432
|
+
</Wrapper>,
|
|
433
|
+
);
|
|
434
|
+
expect(screen.getByText('Fri05Jul')).toBeInTheDocument();
|
|
435
|
+
});
|
|
436
|
+
it('should render the CustomTick component with hour format', () => {
|
|
437
|
+
const { Wrapper } = getWrapper();
|
|
438
|
+
render(
|
|
439
|
+
<Wrapper>
|
|
440
|
+
<CustomTick
|
|
441
|
+
type={{
|
|
442
|
+
type: 'time',
|
|
443
|
+
timeRange: {
|
|
444
|
+
startDate: new Date('2024-07-05'),
|
|
445
|
+
endDate: new Date('2024-07-07'),
|
|
446
|
+
interval: ONE_HOUR_IN_MILLISECONDS,
|
|
447
|
+
},
|
|
448
|
+
}}
|
|
449
|
+
x={100}
|
|
450
|
+
y={100}
|
|
451
|
+
payload={{ value: new Date('2024-07-05T10:00:00').getTime() }}
|
|
452
|
+
visibleTicksCount={10}
|
|
453
|
+
width={100}
|
|
454
|
+
/>
|
|
455
|
+
</Wrapper>,
|
|
456
|
+
);
|
|
457
|
+
expect(screen.getByText('10:00')).toBeInTheDocument();
|
|
458
|
+
});
|
|
459
|
+
it('should render the CustomTick component with minute format', () => {
|
|
460
|
+
const { Wrapper } = getWrapper();
|
|
461
|
+
render(
|
|
462
|
+
<Wrapper>
|
|
463
|
+
<CustomTick
|
|
464
|
+
type={{
|
|
465
|
+
type: 'time',
|
|
466
|
+
timeRange: {
|
|
467
|
+
startDate: new Date('2024-07-05'),
|
|
468
|
+
endDate: new Date('2024-07-07'),
|
|
469
|
+
interval: 1000 * 30,
|
|
470
|
+
},
|
|
471
|
+
}}
|
|
472
|
+
x={100}
|
|
473
|
+
y={100}
|
|
474
|
+
payload={{ value: new Date('2024-07-05T10:00:00').getTime() }}
|
|
475
|
+
visibleTicksCount={10}
|
|
476
|
+
width={100}
|
|
477
|
+
/>
|
|
478
|
+
</Wrapper>,
|
|
479
|
+
);
|
|
480
|
+
expect(
|
|
481
|
+
screen.getByText(new Date('2024-07-05T10:00:00').getTime()),
|
|
482
|
+
).toBeInTheDocument();
|
|
483
|
+
});
|
|
484
|
+
});
|
|
387
485
|
});
|
|
@@ -18,13 +18,9 @@ import { ConstrainedText } from '../constrainedtext/Constrainedtext.component';
|
|
|
18
18
|
import { IconHelp } from '../iconhelper/IconHelper';
|
|
19
19
|
import { Loader } from '../loader/Loader.component';
|
|
20
20
|
import { Text } from '../text/Text.component';
|
|
21
|
-
import {
|
|
22
|
-
formatDate,
|
|
23
|
-
renderTooltipContent,
|
|
24
|
-
UnitRange,
|
|
25
|
-
useChartData,
|
|
26
|
-
} from './utils';
|
|
21
|
+
import { renderTooltipContent, UnitRange, useChartData } from './utils';
|
|
27
22
|
import { useChartLegend } from '../chartlegend/ChartLegendWrapper';
|
|
23
|
+
import { FormattedDateTime } from '../date/FormattedDateTime';
|
|
28
24
|
|
|
29
25
|
const CHART_CONSTANTS = {
|
|
30
26
|
TICK_WIDTH_OFFSET: 5,
|
|
@@ -37,7 +33,7 @@ const CHART_CONSTANTS = {
|
|
|
37
33
|
top: 0,
|
|
38
34
|
bottom: 0,
|
|
39
35
|
},
|
|
40
|
-
}
|
|
36
|
+
};
|
|
41
37
|
|
|
42
38
|
/* ---------------------------------- TYPE ---------------------------------- */
|
|
43
39
|
|
|
@@ -100,7 +96,7 @@ interface CustomTickProps {
|
|
|
100
96
|
x: number;
|
|
101
97
|
y: number;
|
|
102
98
|
payload: {
|
|
103
|
-
value:
|
|
99
|
+
value: number;
|
|
104
100
|
};
|
|
105
101
|
visibleTicksCount: number;
|
|
106
102
|
width: number;
|
|
@@ -109,7 +105,38 @@ interface CustomTickProps {
|
|
|
109
105
|
|
|
110
106
|
/* ---------------------------------- COMPONENTS ---------------------------------- */
|
|
111
107
|
|
|
112
|
-
|
|
108
|
+
/**
|
|
109
|
+
* Formats a date based on the interval
|
|
110
|
+
* @param timestamp - Timestamp
|
|
111
|
+
* @param interval - Interval in milliseconds
|
|
112
|
+
* @returns Formatted string
|
|
113
|
+
*/
|
|
114
|
+
export const formatDate = (
|
|
115
|
+
timestamp: number,
|
|
116
|
+
interval: number,
|
|
117
|
+
): React.ReactNode => {
|
|
118
|
+
const date = new Date(timestamp);
|
|
119
|
+
// More than 24 hours interval - use day and time format
|
|
120
|
+
if (interval > 24 * 60 * 60 * 1000) {
|
|
121
|
+
return (
|
|
122
|
+
<>
|
|
123
|
+
<FormattedDateTime format="chart-date" value={date} />{' '}
|
|
124
|
+
<FormattedDateTime format="time" value={date} />
|
|
125
|
+
</>
|
|
126
|
+
);
|
|
127
|
+
} else if (interval === 24 * 60 * 60 * 1000) {
|
|
128
|
+
// Daily interval - use day format
|
|
129
|
+
return <FormattedDateTime format="chart-date" value={date} />;
|
|
130
|
+
} else if (interval >= 60 * 1000) {
|
|
131
|
+
//Hourly and minute intervals - use minute format
|
|
132
|
+
return <FormattedDateTime format="time" value={date} />;
|
|
133
|
+
} else {
|
|
134
|
+
// minute interval or less - use full timestamp
|
|
135
|
+
return timestamp;
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
export const CustomTick = ({
|
|
113
140
|
x,
|
|
114
141
|
y,
|
|
115
142
|
payload,
|
|
@@ -131,10 +158,11 @@ const CustomTick = ({
|
|
|
131
158
|
overflow="visible"
|
|
132
159
|
>
|
|
133
160
|
<ConstrainedText
|
|
161
|
+
color="textSecondary"
|
|
134
162
|
text={
|
|
135
|
-
<Text variant="Smaller"
|
|
163
|
+
<Text variant="Smaller">
|
|
136
164
|
{type.type === 'time'
|
|
137
|
-
? formatDate(
|
|
165
|
+
? formatDate(payload.value, type.timeRange.interval)
|
|
138
166
|
: String(payload.value)}
|
|
139
167
|
</Text>
|
|
140
168
|
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import styled from 'styled-components';
|
|
2
|
+
import { spacing, Stack, Wrap } from '../../spacing';
|
|
3
|
+
import { Text } from '../text/Text.component';
|
|
4
|
+
import { BarchartBars } from './Barchart.component';
|
|
5
|
+
import { fontSize, fontWeight } from '../../style/theme';
|
|
6
|
+
import { LegendShape } from '../chartlegend/ChartLegend';
|
|
7
|
+
import { FormattedDateTime } from '../date/FormattedDateTime';
|
|
8
|
+
|
|
9
|
+
export const ChartTooltipContainer = styled.div`
|
|
10
|
+
background-color: ${({ theme }) => theme.backgroundLevel1};
|
|
11
|
+
padding: ${spacing.r4} ${spacing.r8};
|
|
12
|
+
border-radius: 4px;
|
|
13
|
+
width: max-content;
|
|
14
|
+
max-width: 40rem;
|
|
15
|
+
border: 1px solid ${({ theme }) => theme.border};
|
|
16
|
+
display: flex;
|
|
17
|
+
font-size: ${fontSize.small};
|
|
18
|
+
flex-direction: column;
|
|
19
|
+
gap: 16px;
|
|
20
|
+
align-items: center;
|
|
21
|
+
`;
|
|
22
|
+
|
|
23
|
+
export const ChartTooltipItem = styled.div<{ isHovered: boolean }>`
|
|
24
|
+
display: flex;
|
|
25
|
+
align-items: center;
|
|
26
|
+
gap: 8px;
|
|
27
|
+
font-weight: ${({ isHovered }) =>
|
|
28
|
+
isHovered ? fontWeight.bold : fontWeight.base};
|
|
29
|
+
`;
|
|
30
|
+
|
|
31
|
+
export const ChartTooltip = <T extends BarchartBars>({
|
|
32
|
+
type,
|
|
33
|
+
currentPoint,
|
|
34
|
+
colorSet,
|
|
35
|
+
}: {
|
|
36
|
+
type: 'time' | 'category';
|
|
37
|
+
currentPoint: {
|
|
38
|
+
category: string | number;
|
|
39
|
+
values: { label: T[number]['label']; value: number; isHovered: boolean }[];
|
|
40
|
+
};
|
|
41
|
+
colorSet: Record<string, string>;
|
|
42
|
+
}) => {
|
|
43
|
+
return (
|
|
44
|
+
<ChartTooltipContainer>
|
|
45
|
+
<Text isEmphazed>
|
|
46
|
+
{type === 'time' ? (
|
|
47
|
+
<FormattedDateTime
|
|
48
|
+
format="long-date"
|
|
49
|
+
value={new Date(currentPoint.category)}
|
|
50
|
+
/>
|
|
51
|
+
) : (
|
|
52
|
+
currentPoint.category
|
|
53
|
+
)}
|
|
54
|
+
</Text>
|
|
55
|
+
<Stack direction="vertical" gap="r8" style={{ width: '100%' }}>
|
|
56
|
+
{currentPoint.values.map((value) => {
|
|
57
|
+
return (
|
|
58
|
+
<Wrap key={value.label}>
|
|
59
|
+
<ChartTooltipItem isHovered={value.isHovered}>
|
|
60
|
+
<LegendShape
|
|
61
|
+
color={colorSet[value.label as keyof typeof colorSet]}
|
|
62
|
+
shape="rectangle"
|
|
63
|
+
chartColors={colorSet}
|
|
64
|
+
/>
|
|
65
|
+
{value.label}
|
|
66
|
+
</ChartTooltipItem>
|
|
67
|
+
<ChartTooltipItem isHovered={value.isHovered}>
|
|
68
|
+
{value.value}
|
|
69
|
+
</ChartTooltipItem>
|
|
70
|
+
</Wrap>
|
|
71
|
+
);
|
|
72
|
+
})}
|
|
73
|
+
</Stack>
|
|
74
|
+
</ChartTooltipContainer>
|
|
75
|
+
);
|
|
76
|
+
};
|
|
@@ -3,7 +3,6 @@ import {
|
|
|
3
3
|
BarchartBars,
|
|
4
4
|
BarchartTooltipFn,
|
|
5
5
|
} from './Barchart.component';
|
|
6
|
-
import { DAY_MONTH_FORMATER, TIME_FORMATER } from '../date/FormattedDateTime';
|
|
7
6
|
import { TooltipContentProps } from 'recharts';
|
|
8
7
|
import { chartColors, ChartColors } from '../../style/theme';
|
|
9
8
|
import { useChartLegend } from '../chartlegend/ChartLegendWrapper';
|
|
@@ -84,31 +83,6 @@ const generateTimeRanges = (
|
|
|
84
83
|
return ranges;
|
|
85
84
|
};
|
|
86
85
|
|
|
87
|
-
/**
|
|
88
|
-
* Formats a date based on the interval
|
|
89
|
-
* @param date - Date object
|
|
90
|
-
* @param interval - Interval in milliseconds
|
|
91
|
-
* @returns Formatted string
|
|
92
|
-
*/
|
|
93
|
-
export const formatDate = (date: Date, interval: number): string => {
|
|
94
|
-
if (interval > 24 * 60 * 60 * 1000) {
|
|
95
|
-
return (
|
|
96
|
-
DAY_MONTH_FORMATER.format(date).replace(/[ ,]/g, '') +
|
|
97
|
-
' ' +
|
|
98
|
-
TIME_FORMATER.format(date)
|
|
99
|
-
);
|
|
100
|
-
} else if (interval === 24 * 60 * 60 * 1000) {
|
|
101
|
-
// Daily or longer intervals - use day format
|
|
102
|
-
return DAY_MONTH_FORMATER.format(date).replace(/[ ,]/g, '');
|
|
103
|
-
} else if (interval >= 60 * 1000) {
|
|
104
|
-
//Handle hourly and minute intervals - use minute format
|
|
105
|
-
return TIME_FORMATER.format(date);
|
|
106
|
-
} else {
|
|
107
|
-
// Second intervals or less - use full timestamp
|
|
108
|
-
return date.toISOString();
|
|
109
|
-
}
|
|
110
|
-
};
|
|
111
|
-
|
|
112
86
|
/**
|
|
113
87
|
* Finds the time range that contains the given date
|
|
114
88
|
* @param date - Data point date
|
|
@@ -149,17 +123,12 @@ export const transformTimeData = <T extends BarchartBars>(
|
|
|
149
123
|
type.timeRange.interval,
|
|
150
124
|
);
|
|
151
125
|
|
|
152
|
-
const categoryMap = new Map<
|
|
153
|
-
string | number,
|
|
154
|
-
{ [key: string]: string | number }
|
|
155
|
-
>();
|
|
126
|
+
const categoryMap = new Map<number, { [key: string]: string | number }>();
|
|
156
127
|
|
|
157
128
|
// Initialize all ranges with zeros
|
|
158
129
|
timeRanges.forEach((range) => {
|
|
159
|
-
// const categoryDisplay = formatDate(range.start, type.timeRange.interval);
|
|
160
|
-
const categoryDisplay = range.start.getTime();
|
|
161
130
|
const initialData: { [key: string]: string | number } = {
|
|
162
|
-
category:
|
|
131
|
+
category: range.start.getTime(),
|
|
163
132
|
};
|
|
164
133
|
barDataKeys.forEach((dataKey) => {
|
|
165
134
|
initialData[dataKey] = 0;
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { render, screen } from '@testing-library/react';
|
|
2
|
+
import { ChartLegend } from './ChartLegend';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { ChartLegendWrapper } from './ChartLegendWrapper';
|
|
5
|
+
import userEvent from '@testing-library/user-event';
|
|
6
|
+
|
|
7
|
+
const colorSet = {
|
|
8
|
+
CPU: 'red',
|
|
9
|
+
Memory: 'blue',
|
|
10
|
+
Disk: 'green',
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
describe('ChartLegend', () => {
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
jest.clearAllMocks();
|
|
16
|
+
});
|
|
17
|
+
it('should render all legend items', () => {
|
|
18
|
+
render(
|
|
19
|
+
<ChartLegendWrapper colorSet={colorSet}>
|
|
20
|
+
<ChartLegend shape="line" />
|
|
21
|
+
</ChartLegendWrapper>,
|
|
22
|
+
);
|
|
23
|
+
expect(screen.getByText('CPU')).toBeInTheDocument();
|
|
24
|
+
expect(screen.getByText('Memory')).toBeInTheDocument();
|
|
25
|
+
expect(screen.getByText('Disk')).toBeInTheDocument();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('should have all items selected by default', () => {
|
|
29
|
+
render(
|
|
30
|
+
<ChartLegendWrapper colorSet={colorSet}>
|
|
31
|
+
<ChartLegend shape="line" />
|
|
32
|
+
</ChartLegendWrapper>,
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
expect(screen.getByLabelText('CPU selected')).toBeInTheDocument();
|
|
36
|
+
expect(screen.getByLabelText('Memory selected')).toBeInTheDocument();
|
|
37
|
+
expect(screen.getByLabelText('Disk selected')).toBeInTheDocument();
|
|
38
|
+
});
|
|
39
|
+
it('should not respond to clicks when disabled', () => {
|
|
40
|
+
render(
|
|
41
|
+
<ChartLegendWrapper colorSet={colorSet}>
|
|
42
|
+
<ChartLegend shape="line" disabled />
|
|
43
|
+
</ChartLegendWrapper>,
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
userEvent.click(screen.getByText('CPU'));
|
|
47
|
+
// If disabled, should not select any items
|
|
48
|
+
expect(screen.getByLabelText('CPU selected')).toBeInTheDocument();
|
|
49
|
+
expect(screen.getByLabelText('Memory selected')).toBeInTheDocument();
|
|
50
|
+
expect(screen.getByLabelText('Disk selected')).toBeInTheDocument();
|
|
51
|
+
|
|
52
|
+
userEvent.click(screen.getByText('Memory'), { metaKey: true });
|
|
53
|
+
// If disabled, should not select any items
|
|
54
|
+
expect(screen.getByLabelText('CPU selected')).toBeInTheDocument();
|
|
55
|
+
expect(screen.getByLabelText('Memory selected')).toBeInTheDocument();
|
|
56
|
+
expect(screen.getByLabelText('Disk selected')).toBeInTheDocument();
|
|
57
|
+
});
|
|
58
|
+
describe('Normal Click Behavior', () => {
|
|
59
|
+
it('should select only clicked item when all are selected', () => {
|
|
60
|
+
render(
|
|
61
|
+
<ChartLegendWrapper colorSet={colorSet}>
|
|
62
|
+
<ChartLegend shape="line" />
|
|
63
|
+
</ChartLegendWrapper>,
|
|
64
|
+
);
|
|
65
|
+
expect(screen.getByLabelText('CPU selected')).toBeInTheDocument();
|
|
66
|
+
expect(screen.getByLabelText('Memory selected')).toBeInTheDocument();
|
|
67
|
+
expect(screen.getByLabelText('Disk selected')).toBeInTheDocument();
|
|
68
|
+
|
|
69
|
+
// Click on CPU should select only CPU
|
|
70
|
+
userEvent.click(screen.getByLabelText('CPU selected'));
|
|
71
|
+
|
|
72
|
+
expect(screen.getByLabelText('CPU selected')).toBeInTheDocument();
|
|
73
|
+
expect(screen.getByLabelText('Memory not selected')).toBeInTheDocument();
|
|
74
|
+
expect(screen.getByLabelText('Disk not selected')).toBeInTheDocument();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('should select all items when clicking on the only selected item', () => {
|
|
78
|
+
render(
|
|
79
|
+
<ChartLegendWrapper colorSet={colorSet}>
|
|
80
|
+
<ChartLegend shape="line" />
|
|
81
|
+
</ChartLegendWrapper>,
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
// First click to select only CPU
|
|
85
|
+
userEvent.click(screen.getByLabelText('CPU selected'));
|
|
86
|
+
|
|
87
|
+
// Second click should select all
|
|
88
|
+
userEvent.click(screen.getByLabelText('CPU selected'));
|
|
89
|
+
|
|
90
|
+
expect(screen.getByLabelText('CPU selected')).toBeInTheDocument();
|
|
91
|
+
expect(screen.getByLabelText('Memory selected')).toBeInTheDocument();
|
|
92
|
+
expect(screen.getByLabelText('Disk selected')).toBeInTheDocument();
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('should select only clicked item when clicking unselected item', () => {
|
|
96
|
+
render(
|
|
97
|
+
<ChartLegendWrapper colorSet={colorSet}>
|
|
98
|
+
<ChartLegend shape="line" />
|
|
99
|
+
</ChartLegendWrapper>,
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
// Select only CPU first
|
|
103
|
+
userEvent.click(screen.getByLabelText('CPU selected'));
|
|
104
|
+
expect(screen.getByLabelText('CPU selected')).toBeInTheDocument();
|
|
105
|
+
expect(screen.getByLabelText('Memory not selected')).toBeInTheDocument();
|
|
106
|
+
|
|
107
|
+
// Click on unselected Memory should select only Memory
|
|
108
|
+
userEvent.click(screen.getByText('Memory'));
|
|
109
|
+
expect(screen.getByLabelText('CPU not selected')).toBeInTheDocument();
|
|
110
|
+
expect(screen.getByLabelText('Memory selected')).toBeInTheDocument();
|
|
111
|
+
expect(screen.getByLabelText('Disk not selected')).toBeInTheDocument();
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
describe('Cmd+Click Behavior', () => {
|
|
116
|
+
it('should add unselected item to selection when cmd+clicking', () => {
|
|
117
|
+
render(
|
|
118
|
+
<ChartLegendWrapper colorSet={colorSet}>
|
|
119
|
+
<ChartLegend shape="line" />
|
|
120
|
+
</ChartLegendWrapper>,
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
// Select only CPU first
|
|
124
|
+
userEvent.click(screen.getByText('CPU'));
|
|
125
|
+
expect(screen.getByLabelText('CPU selected')).toBeInTheDocument();
|
|
126
|
+
expect(screen.getByLabelText('Memory not selected')).toBeInTheDocument();
|
|
127
|
+
|
|
128
|
+
// Cmd+click Memory should add it to selection
|
|
129
|
+
userEvent.click(screen.getByText('Memory'), { metaKey: true });
|
|
130
|
+
expect(screen.getByLabelText('CPU selected')).toBeInTheDocument();
|
|
131
|
+
expect(screen.getByLabelText('Memory selected')).toBeInTheDocument();
|
|
132
|
+
expect(screen.getByLabelText('Disk not selected')).toBeInTheDocument();
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('should remove selected item from selection when cmd+clicking with multiple selected', () => {
|
|
136
|
+
render(
|
|
137
|
+
<ChartLegendWrapper colorSet={colorSet}>
|
|
138
|
+
<ChartLegend shape="line" />
|
|
139
|
+
</ChartLegendWrapper>,
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
// Start with all selected, select only CPU, then add Memory
|
|
143
|
+
userEvent.click(screen.getByText('CPU'));
|
|
144
|
+
userEvent.click(screen.getByText('Memory'), { metaKey: true });
|
|
145
|
+
|
|
146
|
+
expect(screen.getByLabelText('CPU selected')).toBeInTheDocument();
|
|
147
|
+
expect(screen.getByLabelText('Memory selected')).toBeInTheDocument();
|
|
148
|
+
expect(screen.getByLabelText('Disk not selected')).toBeInTheDocument();
|
|
149
|
+
|
|
150
|
+
// Cmd+click CPU should remove it
|
|
151
|
+
userEvent.click(screen.getByText('CPU'), { metaKey: true });
|
|
152
|
+
expect(screen.getByLabelText('CPU not selected')).toBeInTheDocument();
|
|
153
|
+
expect(screen.getByLabelText('Memory selected')).toBeInTheDocument();
|
|
154
|
+
expect(screen.getByLabelText('Disk not selected')).toBeInTheDocument();
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('should select all when cmd+clicking the only selected item', () => {
|
|
158
|
+
render(
|
|
159
|
+
<ChartLegendWrapper colorSet={colorSet}>
|
|
160
|
+
<ChartLegend shape="line" />
|
|
161
|
+
</ChartLegendWrapper>,
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
// Select only CPU
|
|
165
|
+
userEvent.click(screen.getByText('CPU'));
|
|
166
|
+
expect(screen.getByLabelText('CPU selected')).toBeInTheDocument();
|
|
167
|
+
expect(screen.getByLabelText('Memory not selected')).toBeInTheDocument();
|
|
168
|
+
expect(screen.getByLabelText('Disk not selected')).toBeInTheDocument();
|
|
169
|
+
|
|
170
|
+
// Cmd+click the only selected item should select all
|
|
171
|
+
userEvent.click(screen.getByText('CPU'), { metaKey: true });
|
|
172
|
+
expect(screen.getByLabelText('CPU selected')).toBeInTheDocument();
|
|
173
|
+
expect(screen.getByLabelText('Memory selected')).toBeInTheDocument();
|
|
174
|
+
expect(screen.getByLabelText('Disk selected')).toBeInTheDocument();
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it('should work with ctrl+click on Windows/Linux', () => {
|
|
178
|
+
render(
|
|
179
|
+
<ChartLegendWrapper colorSet={colorSet}>
|
|
180
|
+
<ChartLegend shape="line" />
|
|
181
|
+
</ChartLegendWrapper>,
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
// Select only CPU first
|
|
185
|
+
userEvent.click(screen.getByText('CPU'));
|
|
186
|
+
|
|
187
|
+
// Ctrl+click Memory should add it to selection
|
|
188
|
+
userEvent.click(screen.getByText('Memory'), { ctrlKey: true });
|
|
189
|
+
expect(screen.getByLabelText('CPU selected')).toBeInTheDocument();
|
|
190
|
+
expect(screen.getByLabelText('Memory selected')).toBeInTheDocument();
|
|
191
|
+
expect(screen.getByLabelText('Disk not selected')).toBeInTheDocument();
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it('should handle mixed normal and cmd+clicks correctly', () => {
|
|
195
|
+
render(
|
|
196
|
+
<ChartLegendWrapper colorSet={colorSet}>
|
|
197
|
+
<ChartLegend shape="line" />
|
|
198
|
+
</ChartLegendWrapper>,
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
// Normal click to select CPU only
|
|
202
|
+
userEvent.click(screen.getByText('CPU'));
|
|
203
|
+
expect(screen.getByLabelText('CPU selected')).toBeInTheDocument();
|
|
204
|
+
expect(screen.getByLabelText('Memory not selected')).toBeInTheDocument();
|
|
205
|
+
|
|
206
|
+
// Cmd+click to add Memory
|
|
207
|
+
userEvent.click(screen.getByText('Memory'), { metaKey: true });
|
|
208
|
+
expect(screen.getByLabelText('CPU selected')).toBeInTheDocument();
|
|
209
|
+
expect(screen.getByLabelText('Memory selected')).toBeInTheDocument();
|
|
210
|
+
|
|
211
|
+
// Normal click on Disk should select only Disk
|
|
212
|
+
userEvent.click(screen.getByText('Disk'));
|
|
213
|
+
expect(screen.getByLabelText('CPU not selected')).toBeInTheDocument();
|
|
214
|
+
expect(screen.getByLabelText('Memory not selected')).toBeInTheDocument();
|
|
215
|
+
expect(screen.getByLabelText('Disk selected')).toBeInTheDocument();
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
});
|