@centreon/ui 25.7.2 → 25.8.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.
Files changed (43) hide show
  1. package/package.json +1 -1
  2. package/src/Form/CollapsibleGroup.tsx +10 -10
  3. package/src/Form/Form.cypress.spec.tsx +137 -2
  4. package/src/Form/Form.stories.tsx +11 -31
  5. package/src/Form/Form.tsx +2 -0
  6. package/src/Form/Inputs/Grid.tsx +10 -28
  7. package/src/Form/Inputs/SubGroupDivider.tsx +7 -0
  8. package/src/Form/Inputs/Text.tsx +1 -0
  9. package/src/Form/Inputs/index.tsx +6 -0
  10. package/src/Form/Inputs/models.ts +2 -1
  11. package/src/Form/Section/FormSection.tsx +34 -0
  12. package/src/Form/Section/PanelTabs.tsx +13 -0
  13. package/src/Form/Section/navigateToSection.ts +9 -0
  14. package/src/Form/storiesData.tsx +12 -2
  15. package/src/Graph/BarChart/BarChart.cypress.spec.tsx +27 -2
  16. package/src/Graph/BarChart/BarChart.stories.tsx +10 -0
  17. package/src/Graph/BarChart/BarChart.tsx +19 -3
  18. package/src/Graph/BarChart/ResponsiveBarChart.tsx +12 -2
  19. package/src/Graph/Chart/BasicComponents/Lines/StackedLines/index.tsx +7 -1
  20. package/src/Graph/Chart/BasicComponents/Lines/index.tsx +10 -2
  21. package/src/Graph/Chart/Chart.cypress.spec.tsx +66 -13
  22. package/src/Graph/Chart/Chart.stories.tsx +41 -0
  23. package/src/Graph/Chart/Chart.tsx +15 -3
  24. package/src/Graph/Chart/InteractiveComponents/AnchorPoint/RegularAnchorPoint.tsx +8 -2
  25. package/src/Graph/Chart/InteractiveComponents/AnchorPoint/StackedAnchorPoint.tsx +9 -2
  26. package/src/Graph/Chart/InteractiveComponents/AnchorPoint/useTickGraph.ts +19 -2
  27. package/src/Graph/Chart/InteractiveComponents/index.tsx +16 -3
  28. package/src/Graph/Chart/index.tsx +6 -0
  29. package/src/Graph/Chart/models.ts +3 -0
  30. package/src/Graph/common/BaseChart/ChartSvgWrapper.tsx +5 -4
  31. package/src/Graph/common/timeSeries/index.ts +105 -34
  32. package/src/Graph/common/utils.ts +19 -0
  33. package/src/InputField/Select/Autocomplete/Connected/index.tsx +26 -21
  34. package/src/ThemeProvider/palettes.ts +1 -1
  35. package/src/api/models.ts +6 -6
  36. package/src/components/Modal/Modal.stories.tsx +20 -0
  37. package/src/components/Modal/Modal.styles.ts +2 -23
  38. package/src/components/Modal/Modal.tsx +1 -1
  39. package/src/components/Modal/ModalBody.tsx +6 -4
  40. package/src/components/Modal/ModalHeader.tsx +5 -5
  41. package/src/components/Modal/modal.module.css +16 -0
  42. package/src/components/Tabs/Tab.styles.ts +0 -6
  43. package/src/components/Tabs/Tabs.tsx +31 -10
@@ -43,6 +43,8 @@ interface Props {
43
43
  xScale: ScaleTime<number, number>;
44
44
  yScale: ScaleLinear<number, number>;
45
45
  lineStyle: LineStyle | Array<LineStyle>;
46
+ hasSecondUnit?: boolean;
47
+ maxLeftAxisCharacters: number;
46
48
  }
47
49
 
48
50
  const StackLines = ({
@@ -51,7 +53,9 @@ const StackLines = ({
51
53
  yScale,
52
54
  xScale,
53
55
  displayAnchor,
54
- lineStyle
56
+ lineStyle,
57
+ hasSecondUnit,
58
+ maxLeftAxisCharacters
55
59
  }: Props): JSX.Element => {
56
60
  const curveType = getCurveFactory(
57
61
  (equals(type(lineStyle), 'Array')
@@ -101,6 +105,8 @@ const StackLines = ({
101
105
  transparency={transparency}
102
106
  xScale={xScale}
103
107
  yScale={yScale}
108
+ hasSecondUnit={hasSecondUnit}
109
+ maxLeftAxisCharacters={maxLeftAxisCharacters}
104
110
  />
105
111
  )}
106
112
  {style?.showPoints &&
@@ -40,6 +40,8 @@ interface Props extends GlobalAreaLines {
40
40
  xScale: ScaleLinear<number, number>;
41
41
  yScalesPerUnit: Record<string, ScaleLinear<number, number>>;
42
42
  lineStyle: LineStyle | Array<LineStyle>;
43
+ hasSecondUnit?: boolean;
44
+ maxLeftAxisCharacters: number;
43
45
  }
44
46
 
45
47
  const Lines = ({
@@ -56,7 +58,9 @@ const Lines = ({
56
58
  areaRegularLines,
57
59
  scale,
58
60
  scaleLogarithmicBase,
59
- lineStyle
61
+ lineStyle,
62
+ hasSecondUnit,
63
+ maxLeftAxisCharacters
60
64
  }: Props): JSX.Element => {
61
65
  const { stackedLinesData, invertedStackedLinesData } = useStackedLines({
62
66
  lines: displayedLines,
@@ -78,7 +82,9 @@ const Lines = ({
78
82
  graphHeight: height,
79
83
  graphSvgRef,
80
84
  graphWidth: width,
81
- xScale
85
+ xScale,
86
+ hasSecondUnit,
87
+ maxLeftAxisCharacters
82
88
  };
83
89
 
84
90
  return (
@@ -192,6 +198,8 @@ const Lines = ({
192
198
  transparency={transparency}
193
199
  xScale={xScale}
194
200
  yScale={yScale}
201
+ maxLeftAxisCharacters={maxLeftAxisCharacters}
202
+ hasSecondUnit={hasSecondUnit}
195
203
  />
196
204
  )}
197
205
  {style?.showPoints &&
@@ -22,7 +22,15 @@ import WrapperChart from '.';
22
22
  interface Props
23
23
  extends Pick<
24
24
  LineChartProps,
25
- 'legend' | 'tooltip' | 'axis' | 'lineStyle' | 'barStyle' | 'additionalLines'
25
+ | 'legend'
26
+ | 'tooltip'
27
+ | 'axis'
28
+ | 'lineStyle'
29
+ | 'barStyle'
30
+ | 'additionalLines'
31
+ | 'min'
32
+ | 'max'
33
+ | 'boundariesUnit'
26
34
  > {
27
35
  data?: LineChartData;
28
36
  }
@@ -69,7 +77,10 @@ const initialize = ({
69
77
  axis,
70
78
  lineStyle,
71
79
  barStyle,
72
- additionalLines
80
+ additionalLines,
81
+ min,
82
+ max,
83
+ boundariesUnit
73
84
  }: Props): void => {
74
85
  cy.adjustViewport();
75
86
 
@@ -93,6 +104,9 @@ const initialize = ({
93
104
  barStyle={barStyle}
94
105
  tooltip={tooltip}
95
106
  additionalLines={additionalLines}
107
+ min={min}
108
+ max={max}
109
+ boundariesUnit={boundariesUnit}
96
110
  />
97
111
  </Provider>
98
112
  )
@@ -165,8 +179,8 @@ describe('Line chart', () => {
165
179
 
166
180
  cy.contains('06/18/2023').should('be.visible');
167
181
 
168
- cy.contains('0.45 s').should('be.visible');
169
- cy.contains('73.65%').should('be.visible');
182
+ cy.contains('0.4 s').should('be.visible');
183
+ cy.contains('75.64%').should('be.visible');
170
184
 
171
185
  cy.makeSnapshot();
172
186
  });
@@ -188,12 +202,12 @@ describe('Line chart', () => {
188
202
  cy.get('[data-metric="connTime"]').should(
189
203
  'have.attr',
190
204
  'data-highlight',
191
- 'false'
205
+ 'true'
192
206
  );
193
207
  cy.get('[data-metric="hitratio"]').should(
194
208
  'have.attr',
195
209
  'data-highlight',
196
- 'true'
210
+ 'false'
197
211
  );
198
212
 
199
213
  cy.makeSnapshot();
@@ -437,7 +451,7 @@ describe('Line chart', () => {
437
451
 
438
452
  cy.contains(':00 AM').should('be.visible');
439
453
 
440
- cy.get('text[transform="rotate(-35, -2, 241.23251487506278)"]').should(
454
+ cy.get('text[transform="rotate(-35, -2, 198.7929601526369)"]').should(
441
455
  'be.visible'
442
456
  );
443
457
 
@@ -451,8 +465,8 @@ describe('Line chart', () => {
451
465
 
452
466
  cy.contains(':00 AM').should('be.visible');
453
467
 
454
- cy.contains('0.9').should('be.visible');
455
- cy.contains('-0.9').should('be.visible');
468
+ cy.contains('0.8').should('be.visible');
469
+ cy.contains('-0.8').should('be.visible');
456
470
 
457
471
  cy.makeSnapshot();
458
472
  });
@@ -519,7 +533,7 @@ describe('Line chart', () => {
519
533
  checkGraphWidth();
520
534
  cy.contains(':00 AM').should('be.visible');
521
535
  cy.get('circle[cx="250.83333333333334"]').should('be.visible');
522
- cy.get('circle[cy="52.90739222860398"]').should('be.visible');
536
+ cy.get('circle[cy="246.2421135204699"]').should('be.visible');
523
537
 
524
538
  cy.makeSnapshot();
525
539
  });
@@ -733,10 +747,10 @@ describe('Lines and bars', () => {
733
747
  checkGraphWidth();
734
748
 
735
749
  cy.get(
736
- 'path[d="M7.501377410468319,273.1948717814711 h56.51239669421488 h1v1 v100.80512821852892 a1,1 0 0 1 -1,1 h-56.51239669421488 a1,1 0 0 1 -1,-1 v-100.80512821852892 v-1h1z"]'
737
- ).should('be.visible');
750
+ 'path[d="M7.501377410468319,278.09035407759154 h56.51239669421488 h1v1 v95.90964592240846 a1,1 0 0 1 -1,1 h-56.51239669421488 a1,1 0 0 1 -1,-1 v-95.90964592240846 v-1h1z'
751
+ );
738
752
  cy.get(
739
- 'path[d="M24.05509641873278,218.2484747081302 h23.404958677685954 a17.553719008264462,17.553719008264462 0 0 1 17.553719008264462,17.553719008264462 v19.83895905681195 v17.553719008264462h-17.553719008264462 h-23.404958677685954 h-17.553719008264462v-17.553719008264462 v-19.83895905681195 a17.553719008264462,17.553719008264462 0 0 1 17.553719008264462,-17.553719008264462z"]'
753
+ 'path[d="M24.05509641873278,225.7604521029811 h23.404958677685954 a17.553719008264462,17.553719008264462 0 0 1 17.553719008264462,17.553719008264462 v17.222463958081512 v17.553719008264462h-17.553719008264462 h-23.404958677685954 h-17.553719008264462v-17.553719008264462 v-17.222463958081512 a17.553719008264462,17.553719008264462 0 0 1 17.553719008264462,-17.553719008264462z"]'
740
754
  ).should('be.visible');
741
755
 
742
756
  cy.makeSnapshot();
@@ -763,4 +777,43 @@ describe('Lines and bars', () => {
763
777
 
764
778
  cy.makeSnapshot();
765
779
  });
780
+
781
+ it('displays graph according to min and max boundaries', () => {
782
+ initialize({
783
+ data: dataPingServiceLines,
784
+ min: 0.01,
785
+ max: 0.1
786
+ });
787
+
788
+ checkGraphWidth();
789
+
790
+ cy.get('path[data-metric="1"]').should('be.visible');
791
+ cy.get('path[data-metric="3"]').should('be.visible');
792
+ cy.get('path[data-metric="3"]').should('be.visible');
793
+
794
+ cy.contains('0.1 ms').should('be.visible');
795
+ cy.contains('0.1%').should('be.visible');
796
+
797
+ cy.makeSnapshot();
798
+ });
799
+
800
+ it('displays graph according to min and max boundaries for a unit', () => {
801
+ initialize({
802
+ data: dataPingServiceLines,
803
+ min: 0.01,
804
+ max: 0.1,
805
+ boundariesUnit: 'ms'
806
+ });
807
+
808
+ checkGraphWidth();
809
+
810
+ cy.get('path[data-metric="1"]').should('be.visible');
811
+ cy.get('path[data-metric="3"]').should('be.visible');
812
+ cy.get('path[data-metric="3"]').should('be.visible');
813
+
814
+ cy.contains('0.1 ms').should('be.visible');
815
+ cy.contains('2%').should('be.visible');
816
+
817
+ cy.makeSnapshot();
818
+ });
766
819
  });
@@ -717,3 +717,44 @@ export const WithAdditionalLines: Story = {
717
717
  />
718
718
  )
719
719
  };
720
+
721
+ export const linesAndBarsMinMax: Story = {
722
+ argTypes,
723
+ args: {
724
+ ...argumentsData,
725
+ min: 10,
726
+ max: 30,
727
+ lineStyle: {
728
+ curve: 'natural',
729
+ lineWidth: 2,
730
+ showPoints: true
731
+ }
732
+ },
733
+ render: (args) => (
734
+ <WrapperChart
735
+ {...args}
736
+ data={dataPingServiceLinesBarsMixed as unknown as LineChartData}
737
+ />
738
+ )
739
+ };
740
+
741
+ export const linesAndBarsMinMaxForUnit: Story = {
742
+ argTypes,
743
+ args: {
744
+ ...argumentsData,
745
+ min: 10,
746
+ max: 30,
747
+ boundariesUnit: '%',
748
+ lineStyle: {
749
+ curve: 'natural',
750
+ lineWidth: 2,
751
+ showPoints: true
752
+ }
753
+ },
754
+ render: (args) => (
755
+ <WrapperChart
756
+ {...args}
757
+ data={dataPingServiceLinesBarsMixed as unknown as LineChartData}
758
+ />
759
+ )
760
+ };
@@ -112,7 +112,10 @@ const Chart = ({
112
112
  limitLegend,
113
113
  skipIntersectionObserver,
114
114
  transformMatrix,
115
- additionalLines
115
+ additionalLines,
116
+ min,
117
+ max,
118
+ boundariesUnit
116
119
  }: Props): JSX.Element => {
117
120
  const { classes } = useChartStyles();
118
121
 
@@ -191,7 +194,10 @@ const Chart = ({
191
194
  scaleLogarithmicBase: axis?.scaleLogarithmicBase,
192
195
  thresholdUnit,
193
196
  thresholds: (thresholds?.enabled && thresholdValues) || [],
194
- valueGraphHeight: graphHeight - margin.bottom
197
+ valueGraphHeight: graphHeight - margin.bottom,
198
+ min,
199
+ max,
200
+ boundariesUnit
195
201
  }),
196
202
  [
197
203
  linesGraph,
@@ -246,6 +252,8 @@ const Chart = ({
246
252
  [axis?.showGridLines]
247
253
  );
248
254
 
255
+ const hasSecondUnit = useMemo(() => Boolean(secondUnit), [secondUnit]);
256
+
249
257
  if ((!isInViewport && !skipIntersectionObserver) || !height) {
250
258
  return (
251
259
  <Skeleton
@@ -299,7 +307,7 @@ const Chart = ({
299
307
  timeSeries={timeSeries}
300
308
  xScale={xScale}
301
309
  maxAxisCharacters={maxLeftAxisCharacters}
302
- hasSecondUnit={Boolean(secondUnit)}
310
+ hasSecondUnit={hasSecondUnit}
303
311
  >
304
312
  <>
305
313
  {!isEmpty(linesDisplayedAsBar) && (
@@ -327,6 +335,8 @@ const Chart = ({
327
335
  width={graphWidth}
328
336
  xScale={xScale}
329
337
  yScalesPerUnit={yScalesPerUnit}
338
+ hasSecondUnit={hasSecondUnit}
339
+ maxLeftAxisCharacters={maxLeftAxisCharacters}
330
340
  {...shapeLines}
331
341
  />
332
342
  )}
@@ -355,6 +365,8 @@ const Chart = ({
355
365
  }}
356
366
  zoomData={{ ...zoomPreview }}
357
367
  transformMatrix={transformMatrix}
368
+ hasSecondUnit={hasSecondUnit}
369
+ maxLeftAxisCharacters={maxLeftAxisCharacters}
358
370
  />
359
371
  {thresholds?.enabled && (
360
372
  <Thresholds
@@ -15,6 +15,8 @@ interface Props {
15
15
  timeSeries: Array<TimeValue>;
16
16
  xScale: ScaleTime<number, number>;
17
17
  yScale: ScaleLinear<number, number>;
18
+ hasSecondUnit?: boolean;
19
+ maxLeftAxisCharacters: number;
18
20
  }
19
21
 
20
22
  export const getYAnchorPoint = ({
@@ -38,11 +40,15 @@ const RegularAnchorPoint = ({
38
40
  yScale,
39
41
  metric_id,
40
42
  timeSeries,
41
- lineColor
43
+ lineColor,
44
+ maxLeftAxisCharacters,
45
+ hasSecondUnit
42
46
  }: Props): JSX.Element | null => {
43
47
  const { tickAxisBottom: timeTick } = useTickGraph({
44
48
  timeSeries,
45
- xScale
49
+ xScale,
50
+ maxLeftAxisCharacters,
51
+ hasSecondUnit
46
52
  });
47
53
 
48
54
  if (isNil(timeTick)) {
@@ -15,7 +15,10 @@ interface Props {
15
15
  timeSeries: Array<TimeValue>;
16
16
  xScale: ScaleTime<number, number>;
17
17
  yScale: ScaleLinear<number, number>;
18
+ hasSecondUnit?: boolean;
19
+ maxLeftAxisCharacters: number;
18
20
  }
21
+
19
22
  interface GetYAnchorPoint {
20
23
  stackValues: Array<StackValue>;
21
24
  timeTick: Date | null;
@@ -51,11 +54,15 @@ const StackedAnchorPoint = ({
51
54
  yScale,
52
55
  stackValues,
53
56
  timeSeries,
54
- lineColor
57
+ lineColor,
58
+ hasSecondUnit,
59
+ maxLeftAxisCharacters
55
60
  }: Props): JSX.Element | null => {
56
61
  const { tickAxisBottom: timeTick } = useTickGraph({
57
62
  timeSeries,
58
- xScale
63
+ xScale,
64
+ hasSecondUnit,
65
+ maxLeftAxisCharacters
59
66
  });
60
67
 
61
68
  if (isNil(timeTick)) {
@@ -6,6 +6,10 @@ import { useAtomValue } from 'jotai';
6
6
  import useAxisY from '../../../common/Axes/useAxisY';
7
7
  import { getTimeValue } from '../../../common/timeSeries';
8
8
  import { Line, TimeValue } from '../../../common/timeSeries/models';
9
+ import {
10
+ computPixelsToShiftMouse,
11
+ computeGElementMarginLeft
12
+ } from '../../../common/utils';
9
13
  import { margin } from '../../common';
10
14
  import { mousePositionAtom } from '../interactionWithGraphAtoms';
11
15
 
@@ -25,6 +29,8 @@ interface Props {
25
29
  rightScale?: ScaleLinear<number, number>;
26
30
  timeSeries: Array<TimeValue>;
27
31
  xScale: ScaleLinear<number, number>;
32
+ hasSecondUnit?: boolean;
33
+ maxLeftAxisCharacters: number;
28
34
  }
29
35
 
30
36
  const useTickGraph = ({
@@ -33,7 +39,9 @@ const useTickGraph = ({
33
39
  leftScale,
34
40
  rightScale,
35
41
  lines = [],
36
- baseAxis = 1000
42
+ baseAxis = 1000,
43
+ hasSecondUnit,
44
+ maxLeftAxisCharacters
37
45
  }: Props): AnchorPointResult => {
38
46
  const guidingLinesRef = useRef<SVGGElement | null>(null);
39
47
  const [tickAxisBottom, setTickAxisBottom] = useState<Date | null>(null);
@@ -69,8 +77,17 @@ const useTickGraph = ({
69
77
 
70
78
  return;
71
79
  }
80
+ const pixelToShift = computPixelsToShiftMouse(xScale);
72
81
  const mousePositionTimeTick = mousePosition
73
- ? getTimeValue({ timeSeries, x: mousePosition[0], xScale })?.timeTick
82
+ ? getTimeValue({
83
+ timeSeries,
84
+ x: mousePosition[0] - pixelToShift,
85
+ xScale,
86
+ marginLeft: computeGElementMarginLeft({
87
+ maxCharacters: maxLeftAxisCharacters,
88
+ hasSecondUnit
89
+ })
90
+ })?.timeTick
74
91
  : 0;
75
92
  const timeTickValue = mousePosition
76
93
  ? new Date(mousePositionTimeTick || 0)
@@ -37,6 +37,10 @@ import type {
37
37
  InteractedZone as ZoomPreviewModel
38
38
  } from '../models';
39
39
 
40
+ import {
41
+ computPixelsToShiftMouse,
42
+ computeGElementMarginLeft
43
+ } from '../../common/utils';
40
44
  import Annotations from './Annotations';
41
45
  import type { TimelineEvent } from './Annotations/models';
42
46
  import Bar from './Bar';
@@ -80,6 +84,8 @@ interface Props {
80
84
  fx?: (pointX: number) => number;
81
85
  fy?: (pointY: number) => number;
82
86
  };
87
+ hasSecondUnit?: boolean;
88
+ maxLeftAxisCharacters: number;
83
89
  }
84
90
 
85
91
  const InteractionWithGraph = ({
@@ -87,7 +93,9 @@ const InteractionWithGraph = ({
87
93
  commonData,
88
94
  annotationData,
89
95
  timeShiftZonesData,
90
- transformMatrix
96
+ transformMatrix,
97
+ hasSecondUnit,
98
+ maxLeftAxisCharacters
91
99
  }: Props): JSX.Element => {
92
100
  const { classes } = useStyles();
93
101
 
@@ -151,10 +159,15 @@ const InteractionWithGraph = ({
151
159
 
152
160
  return;
153
161
  }
162
+ const pixelToShift = computPixelsToShiftMouse(xScale);
154
163
  const timeValue = getTimeValue({
155
164
  timeSeries,
156
- x: pointPosition[0],
157
- xScale
165
+ x: pointPosition[0] - pixelToShift,
166
+ xScale,
167
+ marginLeft: computeGElementMarginLeft({
168
+ maxCharacters: maxLeftAxisCharacters,
169
+ hasSecondUnit
170
+ })
158
171
  });
159
172
 
160
173
  if (isNil(timeValue)) {
@@ -70,6 +70,9 @@ const WrapperChart = ({
70
70
  getRef,
71
71
  transformMatrix,
72
72
  additionalLines,
73
+ min,
74
+ max,
75
+ boundariesUnit,
73
76
  ...rest
74
77
  }: Props): JSX.Element | null => {
75
78
  const { classes, cx } = useChartStyles();
@@ -125,6 +128,9 @@ const WrapperChart = ({
125
128
  skipIntersectionObserver={rest.skipIntersectionObserver}
126
129
  additionalLines={additionalLines}
127
130
  transformMatrix={transformMatrix}
131
+ min={min}
132
+ max={max}
133
+ boundariesUnit={boundariesUnit}
128
134
  />
129
135
  )}
130
136
  </div>
@@ -122,6 +122,9 @@ export interface LineChartProps {
122
122
  zoomPreview?: InteractedZone;
123
123
  skipIntersectionObserver?: boolean;
124
124
  additionalLines?: Array<AdditionalLineProps>;
125
+ min?: number;
126
+ max?: number;
127
+ boundariesUnit?: string;
125
128
  }
126
129
 
127
130
  export interface Area {
@@ -8,6 +8,7 @@ import { ChartAxis } from '../../Chart/models';
8
8
  import Axes from '../Axes';
9
9
  import Grids from '../Grids';
10
10
  import { Line, TimeValue } from '../timeSeries/models';
11
+ import { computeGElementMarginLeft } from '../utils';
11
12
 
12
13
  interface Props {
13
14
  allUnits: Array<string>;
@@ -58,10 +59,10 @@ const ChartSvgWrapper = ({
58
59
  width="100%"
59
60
  >
60
61
  <Group.Group
61
- left={
62
- maxAxisCharacters * 5 +
63
- (hasSecondUnit ? margin.top * 0.8 : margin.top * 0.6)
64
- }
62
+ left={computeGElementMarginLeft({
63
+ maxCharacters: maxAxisCharacters,
64
+ hasSecondUnit
65
+ })}
65
66
  top={margin.top}
66
67
  >
67
68
  {showGridLines && (