@automattic/charts 1.10.1 → 1.10.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.
@@ -5,7 +5,7 @@ import type { BaseChartProps } from '../../../../types';
5
5
  // Mock the useParentSize hook
6
6
  jest.mock( '@visx/responsive', () => ( {
7
7
  useParentSize: jest.fn( () => ( {
8
- parentRef: { current: null },
8
+ parentRef: () => {},
9
9
  width: 600, // Default width for tests
10
10
  height: 300, // Default height for tests
11
11
  } ) ),
@@ -20,6 +20,32 @@ describe( 'withResponsive', () => {
20
20
 
21
21
  const ResponsiveComponent = withResponsive( MockComponent );
22
22
 
23
+ const { useParentSize } = jest.requireMock( '@visx/responsive' );
24
+ const DEFAULT_SIZE = { parentRef: () => {}, width: 600, height: 300 };
25
+
26
+ // jsdom has no layout engine, so the wrapper's clientHeight (which the contain
27
+ // logic reads from real layout) is always 0. Mock it to simulate the height the
28
+ // parent makes available: 0 means unconstrained (the chart derives its height from
29
+ // width), a positive value simulates a parent that limits the height.
30
+ let mockClientHeight = 0;
31
+ beforeAll( () => {
32
+ Object.defineProperty( window.HTMLElement.prototype, 'clientHeight', {
33
+ configurable: true,
34
+ get() {
35
+ return mockClientHeight;
36
+ },
37
+ } );
38
+ } );
39
+
40
+ // Fully clear the mock (queued returns and implementation) after each test, then
41
+ // re-establish the default implementation, so a per-test override can't bleed
42
+ // into the next test.
43
+ afterEach( () => {
44
+ mockClientHeight = 0;
45
+ useParentSize.mockReset();
46
+ useParentSize.mockImplementation( () => DEFAULT_SIZE );
47
+ } );
48
+
23
49
  describe( 'component dimensions', () => {
24
50
  test( 'passes measured parent width to component', () => {
25
51
  render( <ResponsiveComponent data={ [] } /> );
@@ -34,11 +60,35 @@ describe( 'withResponsive', () => {
34
60
  expect( component ).toHaveStyle( { height: '300px' } );
35
61
  } );
36
62
 
37
- test( 'calculates height from aspectRatio when provided', () => {
63
+ test( 'derives height from width when the parent is taller than the derived height', () => {
64
+ // Parent allows 300px of height; aspectRatio 0.4 derives 600 * 0.4 = 240,
65
+ // which fits, so the chart keeps the full measured width.
66
+ mockClientHeight = 300;
67
+ render( <ResponsiveComponent data={ [] } aspectRatio={ 0.4 } /> );
68
+ const component = screen.getByTestId( 'mock-component' );
69
+ expect( component ).toHaveStyle( { width: '600px', height: '240px' } );
70
+ } );
71
+
72
+ test( 'contains within the parent height when the parent limits the available height', () => {
73
+ // Parent makes 300px of height available; aspectRatio 0.75 would derive
74
+ // 600 * 0.75 = 450, so both axes shrink to fit: 300 / 0.75 = 400.
75
+ mockClientHeight = 300;
38
76
  render( <ResponsiveComponent data={ [] } aspectRatio={ 0.75 } /> );
39
77
  const component = screen.getByTestId( 'mock-component' );
40
- // With aspectRatio, height = width * aspectRatio = 600 * 0.75 = 450
41
- expect( component ).toHaveStyle( { height: '450px' } );
78
+ expect( component ).toHaveStyle( { width: '400px', height: '300px' } );
79
+ } );
80
+
81
+ test( 'derives height from width when the parent has no measured height', () => {
82
+ // parentHeight 0 (unconstrained or not-yet-measured parent): the contain
83
+ // clamp must not run, so the height stays width-derived (600 * 0.4 = 240).
84
+ useParentSize.mockImplementation( () => ( {
85
+ parentRef: () => {},
86
+ width: 600,
87
+ height: 0,
88
+ } ) );
89
+ render( <ResponsiveComponent data={ [] } aspectRatio={ 0.4 } /> );
90
+ const component = screen.getByTestId( 'mock-component' );
91
+ expect( component ).toHaveStyle( { width: '600px', height: '240px' } );
42
92
  } );
43
93
 
44
94
  test( 'respects maxWidth configuration', () => {
@@ -52,13 +102,28 @@ describe( 'withResponsive', () => {
52
102
  const component = screen.getByTestId( 'mock-component' );
53
103
  expect( component ).toHaveAttribute( 'data-size', '200' );
54
104
  } );
105
+
106
+ test( 'assigns the wrapper to an object-shaped parentRef from useParentSize', () => {
107
+ // useParentSize hands back a callback ref in practice, but older versions
108
+ // return a RefObject; the wrapper must populate that shape too.
109
+ const objectRef: { current: HTMLElement | null } = { current: null };
110
+ useParentSize.mockImplementation( () => ( {
111
+ parentRef: objectRef,
112
+ width: 600,
113
+ height: 300,
114
+ } ) );
115
+ render( <ResponsiveComponent data={ [] } /> );
116
+ expect( objectRef.current ).toBe( screen.getByTestId( 'responsive-wrapper' ) );
117
+ } );
55
118
  } );
56
119
 
57
120
  describe( 'wrapper dimensions', () => {
58
- test( 'wrapper defaults to 100% width and height when no props', () => {
121
+ test( 'wrapper has no inline dimensions by default (fills the parent via CSS)', () => {
59
122
  render( <ResponsiveComponent data={ [] } /> );
60
123
  const wrapper = screen.getByTestId( 'responsive-wrapper' );
61
- expect( wrapper ).toHaveStyle( { width: '100%', height: '100%' } );
124
+ expect( wrapper ).toHaveClass( 'container' );
125
+ expect( wrapper ).not.toHaveClass( 'isContained' );
126
+ expect( wrapper ).not.toHaveAttribute( 'style' );
62
127
  } );
63
128
 
64
129
  test( 'wrapper uses explicit width/height for dimensions when provided', () => {
@@ -67,24 +132,170 @@ describe( 'withResponsive', () => {
67
132
  expect( wrapper ).toHaveStyle( { width: '200px', height: '200px' } );
68
133
  } );
69
134
 
70
- test( 'wrapper uses auto height with aspectRatio', () => {
135
+ test( 'wrapper gets the contained class with aspectRatio (centering lives in CSS)', () => {
71
136
  render( <ResponsiveComponent data={ [] } aspectRatio={ 0.5 } /> );
72
137
  const wrapper = screen.getByTestId( 'responsive-wrapper' );
73
- expect( wrapper ).toHaveStyle( { width: '100%', height: 'auto' } );
138
+ expect( wrapper ).toHaveClass( 'isContained' );
74
139
  } );
75
140
 
76
- test( 'wrapper expresses aspectRatio in CSS and caps at maxWidth', () => {
77
- render( <ResponsiveComponent data={ [] } aspectRatio={ 0.5 } maxWidth={ 800 } /> );
78
- const wrapper = screen.getByTestId( 'responsive-wrapper' );
79
- // CSS aspect-ratio is width/height, so 1 / 0.5 = 2.
80
- expect( wrapper ).toHaveStyle( { aspectRatio: '2', maxWidth: '800px' } );
141
+ test( 'no content box is rendered without an aspectRatio', () => {
142
+ render( <ResponsiveComponent data={ [] } /> );
143
+ expect( screen.queryByTestId( 'responsive-content' ) ).not.toBeInTheDocument();
81
144
  } );
82
145
 
83
- test( 'wrapper omits the maxWidth cap when an explicit width is set', () => {
84
- render( <ResponsiveComponent data={ [] } aspectRatio={ 0.5 } width={ 300 } /> );
85
- const wrapper = screen.getByTestId( 'responsive-wrapper' );
86
- expect( wrapper ).toHaveStyle( { aspectRatio: '2', width: '300px' } );
87
- expect( wrapper ).not.toHaveStyle( { maxWidth: '1200px' } );
146
+ test( 'renders the contained chart in an inner content box sized to the aspect ratio', () => {
147
+ // aspectRatio 0.4: 600 * 0.4 = 240 fits the 300px parent.
148
+ render( <ResponsiveComponent data={ [] } aspectRatio={ 0.4 } /> );
149
+ const content = screen.getByTestId( 'responsive-content' );
150
+ expect( content ).toHaveStyle( { width: '600px', height: '240px' } );
151
+ } );
152
+
153
+ test( 'content box contains to the parent height when the parent limits the available height', () => {
154
+ // aspectRatio 0.75 derives 450 > the 300px the parent allows, so both axes
155
+ // shrink to fit: width 400 (300 / 0.75) × height 300.
156
+ mockClientHeight = 300;
157
+ render( <ResponsiveComponent data={ [] } aspectRatio={ 0.75 } /> );
158
+ const content = screen.getByTestId( 'responsive-content' );
159
+ expect( content ).toHaveStyle( { width: '400px', height: '300px' } );
160
+ } );
161
+ } );
162
+
163
+ describe( 'resize behavior', () => {
164
+ test( 'grows on widen in a fluid (unconstrained-height) parent', () => {
165
+ // Unconstrained parent (mockClientHeight 0): the chart derives its height
166
+ // from width and must grow when the parent widens, not deadlock.
167
+ useParentSize.mockImplementation( () => ( {
168
+ parentRef: () => {},
169
+ width: 600,
170
+ height: 0,
171
+ } ) );
172
+ const { rerender } = render( <ResponsiveComponent data={ [] } aspectRatio={ 0.4 } /> );
173
+ expect( screen.getByTestId( 'mock-component' ) ).toHaveStyle( {
174
+ width: '600px',
175
+ height: '240px',
176
+ } );
177
+
178
+ useParentSize.mockImplementation( () => ( {
179
+ parentRef: () => {},
180
+ width: 900,
181
+ height: 0,
182
+ } ) );
183
+ rerender( <ResponsiveComponent data={ [] } aspectRatio={ 0.4 } /> );
184
+ expect( screen.getByTestId( 'mock-component' ) ).toHaveStyle( {
185
+ width: '900px',
186
+ height: '360px',
187
+ } );
188
+ } );
189
+
190
+ test( 'stays contained on widen when the parent limits the height', () => {
191
+ // Parent allows 150px of height. aspectRatio 0.4 derives 240 at width 600 and
192
+ // 360 at width 900 — both exceed 150 — so the chart stays contained at
193
+ // 375 × 150 (150 / 0.4 = 375) across the widen rather than overflowing.
194
+ mockClientHeight = 150;
195
+ useParentSize.mockImplementation( () => ( {
196
+ parentRef: () => {},
197
+ width: 600,
198
+ height: 150,
199
+ } ) );
200
+ const { rerender } = render( <ResponsiveComponent data={ [] } aspectRatio={ 0.4 } /> );
201
+ expect( screen.getByTestId( 'mock-component' ) ).toHaveStyle( {
202
+ width: '375px',
203
+ height: '150px',
204
+ } );
205
+
206
+ useParentSize.mockImplementation( () => ( {
207
+ parentRef: () => {},
208
+ width: 900,
209
+ height: 150,
210
+ } ) );
211
+ rerender( <ResponsiveComponent data={ [] } aspectRatio={ 0.4 } /> );
212
+ expect( screen.getByTestId( 'mock-component' ) ).toHaveStyle( {
213
+ width: '375px',
214
+ height: '150px',
215
+ } );
216
+ } );
217
+
218
+ test( 'releases containment when the parent grows tall enough', () => {
219
+ // Parent starts at 150px of height, so aspectRatio 0.4 (derives 240 at width
220
+ // 600) is contained to 375 × 150.
221
+ mockClientHeight = 150;
222
+ useParentSize.mockImplementation( () => ( {
223
+ parentRef: () => {},
224
+ width: 600,
225
+ height: 150,
226
+ } ) );
227
+ const { rerender } = render( <ResponsiveComponent data={ [] } aspectRatio={ 0.4 } /> );
228
+ expect( screen.getByTestId( 'mock-component' ) ).toHaveStyle( {
229
+ width: '375px',
230
+ height: '150px',
231
+ } );
232
+
233
+ // Parent grows to 300px — now taller than the 240px derived height — so
234
+ // containment releases and the chart returns to its full width-derived size.
235
+ mockClientHeight = 300;
236
+ useParentSize.mockImplementation( () => ( {
237
+ parentRef: () => {},
238
+ width: 600,
239
+ height: 300,
240
+ } ) );
241
+ rerender( <ResponsiveComponent data={ [] } aspectRatio={ 0.4 } /> );
242
+ expect( screen.getByTestId( 'mock-component' ) ).toHaveStyle( {
243
+ width: '600px',
244
+ height: '240px',
245
+ } );
246
+ } );
247
+
248
+ test( 'and re-contains to the new height when the parent shrinks further', () => {
249
+ // Parent allows 300px; aspectRatio 0.75 derives 450, so it contains to 400 × 300.
250
+ mockClientHeight = 300;
251
+ useParentSize.mockImplementation( () => ( {
252
+ parentRef: () => {},
253
+ width: 600,
254
+ height: 300,
255
+ } ) );
256
+ const { rerender } = render( <ResponsiveComponent data={ [] } aspectRatio={ 0.75 } /> );
257
+ expect( screen.getByTestId( 'mock-component' ) ).toHaveStyle( {
258
+ width: '400px',
259
+ height: '300px',
260
+ } );
261
+
262
+ // Parent shrinks to 150px — still shorter than the 450px derived height — so the
263
+ // chart re-contains to the new available height: 150 / 0.75 = 200 × 150.
264
+ mockClientHeight = 150;
265
+ useParentSize.mockImplementation( () => ( {
266
+ parentRef: () => {},
267
+ width: 600,
268
+ height: 150,
269
+ } ) );
270
+ rerender( <ResponsiveComponent data={ [] } aspectRatio={ 0.75 } /> );
271
+ expect( screen.getByTestId( 'mock-component' ) ).toHaveStyle( {
272
+ width: '200px',
273
+ height: '150px',
274
+ } );
275
+ } );
276
+
277
+ test( 'releases containment when aspectRatio is removed', () => {
278
+ // Contained first (derived 450 > the 300px parent → 400 × 300).
279
+ mockClientHeight = 300;
280
+ useParentSize.mockImplementation( () => ( {
281
+ parentRef: () => {},
282
+ width: 600,
283
+ height: 300,
284
+ } ) );
285
+ const { rerender } = render( <ResponsiveComponent data={ [] } aspectRatio={ 0.75 } /> );
286
+ expect( screen.getByTestId( 'mock-component' ) ).toHaveStyle( {
287
+ width: '400px',
288
+ height: '300px',
289
+ } );
290
+
291
+ // Dropping aspectRatio clears the containment and fills the parent again; the
292
+ // inner content box is gone.
293
+ rerender( <ResponsiveComponent data={ [] } /> );
294
+ expect( screen.queryByTestId( 'responsive-content' ) ).not.toBeInTheDocument();
295
+ expect( screen.getByTestId( 'mock-component' ) ).toHaveStyle( {
296
+ width: '600px',
297
+ height: '300px',
298
+ } );
88
299
  } );
89
300
  } );
90
301
 
@@ -1,5 +1,24 @@
1
1
  .container {
2
- // Allow wrapper to shrink in flex layouts
2
+ // Fill the parent by default so the wrapper's live height reflects the
3
+ // real available space. JS sets width/height only when passed explicitly.
4
+ width: 100%;
5
+ height: 100%;
6
+ // Allow the wrapper to shrink in flex layouts
7
+ min-width: 0;
8
+ min-height: 0;
9
+ }
10
+
11
+ // With an aspectRatio, center the contained box horizontally and top-align it.
12
+ .isContained {
13
+ display: flex;
14
+ justify-content: center;
15
+ align-items: flex-start;
16
+ }
17
+
18
+ // The contain box. Its width/height come from JS; keep it from being stretched
19
+ // or shrunk by the flex parent.
20
+ .content {
21
+ flex: 0 0 auto;
3
22
  min-width: 0;
4
23
  min-height: 0;
5
24
  }
@@ -1,4 +1,6 @@
1
1
  import { useParentSize } from '@visx/responsive';
2
+ import clsx from 'clsx';
3
+ import { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';
2
4
  import styles from './with-responsive.module.scss';
3
5
  import type { BaseChartProps } from '../../../types';
4
6
  import type { ComponentType } from 'react';
@@ -16,7 +18,11 @@ export type ResponsiveConfig = {
16
18
  maxWidth?: number;
17
19
  /**
18
20
  * The aspect ratio of the chart (height = width * aspectRatio).
19
- * When provided, height is calculated from width.
21
+ * When provided, the chart keeps this ratio and is contained within the
22
+ * parent on both axes: it fills the available width and derives its height,
23
+ * but if the parent is shorter than that derived height it shrinks both axes
24
+ * to fit rather than overflowing. When it is narrower than the parent (the
25
+ * height-constrained case) it is centered horizontally.
20
26
  * When omitted, the chart fills the parent container's height.
21
27
  */
22
28
  aspectRatio?: number;
@@ -26,35 +32,9 @@ export type ResponsiveConfig = {
26
32
  resizeDebounceTime?: number;
27
33
  };
28
34
 
29
- const useResponsiveDimensions = ( {
30
- resizeDebounceTime = 300,
31
- maxWidth = 1200,
32
- aspectRatio,
33
- }: ResponsiveConfig ) => {
34
- const {
35
- parentRef,
36
- width: parentWidth,
37
- height: parentHeight,
38
- } = useParentSize( {
39
- debounceTime: resizeDebounceTime,
40
- enableDebounceLeadingCall: true,
41
- } );
42
-
43
- const containerWidth = parentWidth > 0 ? Math.min( parentWidth, maxWidth ) : 0;
44
- const containerHeight = aspectRatio !== undefined ? containerWidth * aspectRatio : parentHeight;
45
-
46
- return {
47
- parentRef,
48
- width: containerWidth,
49
- height: containerHeight,
50
- /**
51
- * Whether an aspectRatio was provided. Used to determine container
52
- * height styling: 'auto' when true (height derived from width),
53
- * '100%' when false (fill parent container).
54
- */
55
- hasAspectRatio: aspectRatio !== undefined,
56
- };
57
- };
35
+ // useLayoutEffect on the client (so containment is resolved before paint, no flash),
36
+ // useEffect on the server to avoid React's SSR warning.
37
+ const useIsomorphicLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect;
58
38
 
59
39
  /**
60
40
  * A higher-order component that provides responsive dimensions
@@ -77,51 +57,120 @@ export function withResponsive< T extends Exclude< BaseChartProps< unknown >, 'o
77
57
  }: Omit< T, 'width' | 'height' | 'size' > & DimensionProps & ResponsiveConfig ) {
78
58
  const {
79
59
  parentRef,
80
- width: measuredWidth,
81
- height: measuredHeight,
82
- hasAspectRatio,
83
- } = useResponsiveDimensions( {
84
- resizeDebounceTime,
85
- maxWidth,
86
- aspectRatio,
60
+ width: parentWidth,
61
+ height: parentHeight,
62
+ } = useParentSize( {
63
+ debounceTime: resizeDebounceTime,
64
+ enableDebounceLeadingCall: true,
87
65
  } );
88
66
 
89
- // Use measured dimensions, but fall back to explicit width/height props if measurement returns 0
90
- // (e.g., during initial render or in test environments without DOM measurement).
91
- // Do not use size here — size controls chart element dimensions (e.g. pie diameter), not container dimensions.
92
- const effectiveWidth = measuredWidth || width || 0;
93
- const effectiveHeight = measuredHeight || height || 0;
67
+ const hasAspectRatio = aspectRatio !== undefined && aspectRatio > 0;
68
+
69
+ // Keep our own handle on the wrapper so we can read its live height below, and
70
+ // still hand the node to useParentSize's ref (a callback ref in practice; guard
71
+ // the object-ref shape too).
72
+ const wrapperRef = useRef< HTMLDivElement | null >( null );
73
+ const setWrapperRef = useCallback(
74
+ ( node: HTMLDivElement | null ) => {
75
+ wrapperRef.current = node;
76
+ if ( typeof parentRef === 'function' ) {
77
+ parentRef( node );
78
+ } else if ( parentRef ) {
79
+ ( parentRef as { current: HTMLDivElement | null } ).current = node;
80
+ }
81
+ },
82
+ [ parentRef ]
83
+ );
94
84
 
95
- const defaultHeight = hasAspectRatio ? 'auto' : '100%';
96
- // Express the aspect ratio in CSS so the container height tracks its width
97
- // fluidly, rather than snapping to a debounced measured height. Cap the width
98
- // at maxWidth so the CSS-derived height matches the maxWidth-capped content
99
- // (the wrapped chart is sized from the capped `measuredWidth`).
100
- const aspectRatioStyle =
101
- hasAspectRatio && aspectRatio
102
- ? {
103
- aspectRatio: `${ 1 / aspectRatio }`,
104
- maxWidth: width === undefined ? maxWidth : undefined,
105
- }
106
- : null;
85
+ // The parent's available height when it actually constrains the chart, else
86
+ // null (the chart derives its height from width and grows freely).
87
+ const [ containedHeight, setContainedHeight ] = useState< number | null >( null );
88
+
89
+ // Cap the available width at maxWidth unless an explicit width pins it. Before
90
+ // measurement resolves, fall back to the explicit width.
91
+ const cap = width === undefined ? maxWidth : Infinity;
92
+ const availableWidth = parentWidth > 0 ? Math.min( parentWidth, cap ) : width ?? 0;
93
+
94
+ let boxWidth = availableWidth;
95
+ let boxHeight: number;
96
+ if ( hasAspectRatio ) {
97
+ const derivedHeight = availableWidth * aspectRatio;
98
+ if ( containedHeight !== null && derivedHeight > containedHeight ) {
99
+ boxHeight = containedHeight;
100
+ boxWidth = boxHeight / aspectRatio;
101
+ } else {
102
+ boxHeight = derivedHeight;
103
+ }
104
+ } else {
105
+ boxHeight = parentHeight > 0 ? parentHeight : height ?? 0;
106
+ }
107
+
108
+ // Decide containment from the wrapper's real height, measured after layout.
109
+ // useParentSize's height is no good here: with height:100% and no definite
110
+ // parent height it just echoes our own content back, so clamping against it
111
+ // would freeze the chart at its current width when the container widens.
112
+ // Compare the post-layout clientHeight to the height we drew: shorter means the
113
+ // parent really constrains us → contain to it; equal means it is only our own
114
+ // content → let it grow. Once contained, stay contained until the parent is tall
115
+ // enough for the full derived height, so a now-fitting chart doesn't oscillate.
116
+ // (parentHeight is in the deps only to re-run this; the value used is clientHeight.)
117
+ useIsomorphicLayoutEffect( () => {
118
+ if ( ! hasAspectRatio ) {
119
+ if ( containedHeight !== null ) {
120
+ setContainedHeight( null );
121
+ }
122
+ return;
123
+ }
124
+ const available = wrapperRef.current?.clientHeight ?? 0;
125
+ const derivedHeight = availableWidth * aspectRatio;
126
+ if ( containedHeight === null ) {
127
+ if ( available > 0 && derivedHeight > available + 1 ) {
128
+ setContainedHeight( available );
129
+ }
130
+ } else if ( available >= derivedHeight - 1 ) {
131
+ setContainedHeight( null );
132
+ } else if ( Math.abs( available - containedHeight ) > 1 ) {
133
+ setContainedHeight( available );
134
+ }
135
+ }, [ hasAspectRatio, availableWidth, aspectRatio, containedHeight, parentHeight ] );
136
+
137
+ const wrappedComponent = (
138
+ <WrappedComponent
139
+ width={ boxWidth }
140
+ height={ boxHeight }
141
+ size={ size }
142
+ { ...( chartProps as T ) }
143
+ />
144
+ );
107
145
 
146
+ // The outer element fills the parent (via CSS) so its live height reflects the
147
+ // real available space; JS sets width/height only when they are passed
148
+ // explicitly. Static layout (fill, flex centering) lives in the stylesheet. With
149
+ // an aspectRatio the chart is placed in an inner box sized to the contained
150
+ // dimensions (centered by CSS) so it keeps its proportions and fits within the
151
+ // parent on both axes. Charts that fill their container via CSS (e.g. the heatmap
152
+ // grid) then track that box. Without an aspectRatio the chart fills the parent.
108
153
  return (
109
154
  <div
110
- ref={ parentRef }
155
+ ref={ setWrapperRef }
111
156
  data-testid="responsive-wrapper"
112
- className={ styles.container }
157
+ className={ clsx( styles.container, hasAspectRatio && styles.isContained ) }
113
158
  style={ {
114
- width: width ?? '100%',
115
- height: height ?? defaultHeight,
116
- ...aspectRatioStyle,
159
+ ...( width !== undefined ? { width } : null ),
160
+ ...( height !== undefined ? { height } : null ),
117
161
  } }
118
162
  >
119
- <WrappedComponent
120
- width={ effectiveWidth }
121
- height={ effectiveHeight }
122
- size={ size }
123
- { ...( chartProps as T ) }
124
- />
163
+ { hasAspectRatio ? (
164
+ <div
165
+ data-testid="responsive-content"
166
+ className={ styles.content }
167
+ style={ { width: boxWidth, height: boxHeight } }
168
+ >
169
+ { wrappedComponent }
170
+ </div>
171
+ ) : (
172
+ wrappedComponent
173
+ ) }
125
174
  </div>
126
175
  );
127
176
  };