@automattic/charts 0.57.0 → 0.59.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 (267) hide show
  1. package/CHANGELOG.md +36 -2
  2. package/README.md +7 -54
  3. package/dist/index.cjs +9607 -21
  4. package/dist/index.cjs.map +1 -1
  5. package/dist/index.css +32 -49
  6. package/dist/index.css.map +1 -1
  7. package/dist/index.d.cts +1612 -33
  8. package/dist/index.d.ts +1612 -33
  9. package/dist/index.js +9640 -54
  10. package/dist/index.js.map +1 -1
  11. package/package.json +9 -126
  12. package/src/charts/bar-chart/bar-chart.module.scss +0 -5
  13. package/src/charts/bar-chart/bar-chart.tsx +142 -149
  14. package/src/charts/bar-chart/test/bar-chart.test.tsx +48 -31
  15. package/src/charts/leaderboard-chart/leaderboard-chart.tsx +54 -74
  16. package/src/charts/leaderboard-chart/test/leaderboard-chart.test.tsx +4 -5
  17. package/src/charts/leaderboard-chart/types.ts +1 -11
  18. package/src/charts/line-chart/line-chart.module.scss +0 -5
  19. package/src/charts/line-chart/line-chart.tsx +202 -193
  20. package/src/charts/line-chart/private/line-chart-annotations-overlay.tsx +1 -2
  21. package/src/charts/line-chart/test/line-chart.test.tsx +49 -27
  22. package/src/charts/line-chart/types.ts +0 -1
  23. package/src/charts/pie-chart/pie-chart.module.scss +2 -10
  24. package/src/charts/pie-chart/pie-chart.tsx +212 -212
  25. package/src/charts/pie-chart/test/composition-api.test.tsx +44 -3
  26. package/src/charts/pie-chart/test/pie-chart.test.tsx +51 -44
  27. package/src/charts/pie-semi-circle-chart/pie-semi-circle-chart.module.scss +2 -8
  28. package/src/charts/pie-semi-circle-chart/pie-semi-circle-chart.tsx +166 -168
  29. package/src/charts/pie-semi-circle-chart/test/pie-semi-circle-chart.test.tsx +58 -30
  30. package/src/charts/private/chart-composition/index.ts +2 -0
  31. package/src/charts/private/chart-composition/render-legend-slot.ts +22 -0
  32. package/src/charts/private/chart-composition/test/render-legend-slot.test.tsx +60 -0
  33. package/src/charts/private/chart-composition/test/use-chart-children.test.tsx +91 -0
  34. package/src/charts/private/chart-composition/use-chart-children.ts +34 -2
  35. package/src/charts/private/chart-layout/chart-layout.module.scss +7 -0
  36. package/src/charts/private/chart-layout/chart-layout.tsx +106 -0
  37. package/src/charts/private/chart-layout/index.ts +2 -0
  38. package/src/charts/private/chart-layout/test/chart-layout.test.tsx +167 -0
  39. package/src/charts/private/single-chart-context/single-chart-context.tsx +2 -2
  40. package/src/charts/private/svg-empty-state/index.ts +1 -0
  41. package/src/charts/private/svg-empty-state/svg-empty-state.module.scss +7 -0
  42. package/src/charts/private/svg-empty-state/svg-empty-state.tsx +40 -0
  43. package/src/components/legend/hooks/test/use-chart-legend-items.test.tsx +12 -8
  44. package/src/components/legend/hooks/use-chart-legend-items.ts +12 -13
  45. package/src/components/legend/index.ts +1 -8
  46. package/src/components/legend/legend.tsx +33 -8
  47. package/src/components/legend/private/base-legend.module.scss +19 -37
  48. package/src/components/legend/private/base-legend.tsx +0 -2
  49. package/src/components/legend/test/legend.test.tsx +93 -1
  50. package/src/components/legend/types.ts +7 -34
  51. package/src/hooks/index.ts +1 -1
  52. package/src/hooks/use-data-with-percentages.ts +24 -0
  53. package/src/hooks/use-interactive-legend-data.ts +18 -15
  54. package/src/index.ts +66 -9
  55. package/src/providers/chart-context/global-charts-provider.tsx +7 -1
  56. package/src/providers/chart-context/hooks/use-chart-registration.ts +2 -1
  57. package/src/providers/chart-context/types.ts +2 -2
  58. package/src/types.ts +110 -45
  59. package/src/utils/date-parsing.ts +10 -1
  60. package/src/utils/test/date-parsing.test.ts +12 -0
  61. package/src/utils/test/resolve-css-var.test.ts +4 -2
  62. package/tsup.config.ts +1 -1
  63. package/dist/base-tooltip-DOq93wjU.d.cts +0 -38
  64. package/dist/base-tooltip-DOq93wjU.d.ts +0 -38
  65. package/dist/charts/bar-chart/index.cjs +0 -15
  66. package/dist/charts/bar-chart/index.cjs.map +0 -1
  67. package/dist/charts/bar-chart/index.css +0 -153
  68. package/dist/charts/bar-chart/index.css.map +0 -1
  69. package/dist/charts/bar-chart/index.d.cts +0 -37
  70. package/dist/charts/bar-chart/index.d.ts +0 -37
  71. package/dist/charts/bar-chart/index.js +0 -15
  72. package/dist/charts/bar-chart/index.js.map +0 -1
  73. package/dist/charts/bar-list-chart/index.cjs +0 -16
  74. package/dist/charts/bar-list-chart/index.cjs.map +0 -1
  75. package/dist/charts/bar-list-chart/index.css +0 -153
  76. package/dist/charts/bar-list-chart/index.css.map +0 -1
  77. package/dist/charts/bar-list-chart/index.d.cts +0 -92
  78. package/dist/charts/bar-list-chart/index.d.ts +0 -92
  79. package/dist/charts/bar-list-chart/index.js +0 -16
  80. package/dist/charts/bar-list-chart/index.js.map +0 -1
  81. package/dist/charts/conversion-funnel-chart/index.cjs +0 -11
  82. package/dist/charts/conversion-funnel-chart/index.cjs.map +0 -1
  83. package/dist/charts/conversion-funnel-chart/index.css +0 -251
  84. package/dist/charts/conversion-funnel-chart/index.css.map +0 -1
  85. package/dist/charts/conversion-funnel-chart/index.d.cts +0 -97
  86. package/dist/charts/conversion-funnel-chart/index.d.ts +0 -97
  87. package/dist/charts/conversion-funnel-chart/index.js +0 -11
  88. package/dist/charts/conversion-funnel-chart/index.js.map +0 -1
  89. package/dist/charts/geo-chart/index.cjs +0 -13
  90. package/dist/charts/geo-chart/index.cjs.map +0 -1
  91. package/dist/charts/geo-chart/index.css +0 -117
  92. package/dist/charts/geo-chart/index.css.map +0 -1
  93. package/dist/charts/geo-chart/index.d.cts +0 -67
  94. package/dist/charts/geo-chart/index.d.ts +0 -67
  95. package/dist/charts/geo-chart/index.js +0 -13
  96. package/dist/charts/geo-chart/index.js.map +0 -1
  97. package/dist/charts/leaderboard-chart/index.cjs +0 -20
  98. package/dist/charts/leaderboard-chart/index.cjs.map +0 -1
  99. package/dist/charts/leaderboard-chart/index.css +0 -172
  100. package/dist/charts/leaderboard-chart/index.css.map +0 -1
  101. package/dist/charts/leaderboard-chart/index.d.cts +0 -46
  102. package/dist/charts/leaderboard-chart/index.d.ts +0 -46
  103. package/dist/charts/leaderboard-chart/index.js +0 -20
  104. package/dist/charts/leaderboard-chart/index.js.map +0 -1
  105. package/dist/charts/line-chart/index.cjs +0 -15
  106. package/dist/charts/line-chart/index.cjs.map +0 -1
  107. package/dist/charts/line-chart/index.css +0 -225
  108. package/dist/charts/line-chart/index.css.map +0 -1
  109. package/dist/charts/line-chart/index.d.cts +0 -99
  110. package/dist/charts/line-chart/index.d.ts +0 -99
  111. package/dist/charts/line-chart/index.js +0 -15
  112. package/dist/charts/line-chart/index.js.map +0 -1
  113. package/dist/charts/pie-chart/index.cjs +0 -18
  114. package/dist/charts/pie-chart/index.cjs.map +0 -1
  115. package/dist/charts/pie-chart/index.css +0 -143
  116. package/dist/charts/pie-chart/index.css.map +0 -1
  117. package/dist/charts/pie-chart/index.d.cts +0 -97
  118. package/dist/charts/pie-chart/index.d.ts +0 -97
  119. package/dist/charts/pie-chart/index.js +0 -18
  120. package/dist/charts/pie-chart/index.js.map +0 -1
  121. package/dist/charts/pie-semi-circle-chart/index.cjs +0 -17
  122. package/dist/charts/pie-semi-circle-chart/index.cjs.map +0 -1
  123. package/dist/charts/pie-semi-circle-chart/index.css +0 -144
  124. package/dist/charts/pie-semi-circle-chart/index.css.map +0 -1
  125. package/dist/charts/pie-semi-circle-chart/index.d.cts +0 -94
  126. package/dist/charts/pie-semi-circle-chart/index.d.ts +0 -94
  127. package/dist/charts/pie-semi-circle-chart/index.js +0 -17
  128. package/dist/charts/pie-semi-circle-chart/index.js.map +0 -1
  129. package/dist/charts/sparkline/index.cjs +0 -16
  130. package/dist/charts/sparkline/index.cjs.map +0 -1
  131. package/dist/charts/sparkline/index.css +0 -242
  132. package/dist/charts/sparkline/index.css.map +0 -1
  133. package/dist/charts/sparkline/index.d.cts +0 -113
  134. package/dist/charts/sparkline/index.d.ts +0 -113
  135. package/dist/charts/sparkline/index.js +0 -16
  136. package/dist/charts/sparkline/index.js.map +0 -1
  137. package/dist/chunk-2A34OA5O.cjs +0 -51
  138. package/dist/chunk-2A34OA5O.cjs.map +0 -1
  139. package/dist/chunk-2NCY7R4G.js +0 -3897
  140. package/dist/chunk-2NCY7R4G.js.map +0 -1
  141. package/dist/chunk-32DH6JDF.js +0 -1263
  142. package/dist/chunk-32DH6JDF.js.map +0 -1
  143. package/dist/chunk-4OPFE4RM.js +0 -614
  144. package/dist/chunk-4OPFE4RM.js.map +0 -1
  145. package/dist/chunk-6CCZL2JJ.js +0 -63
  146. package/dist/chunk-6CCZL2JJ.js.map +0 -1
  147. package/dist/chunk-77OKCVQN.cjs +0 -421
  148. package/dist/chunk-77OKCVQN.cjs.map +0 -1
  149. package/dist/chunk-7FQX4ALL.cjs +0 -219
  150. package/dist/chunk-7FQX4ALL.cjs.map +0 -1
  151. package/dist/chunk-ASLARV7L.cjs +0 -81
  152. package/dist/chunk-ASLARV7L.cjs.map +0 -1
  153. package/dist/chunk-BCX5THDQ.js +0 -403
  154. package/dist/chunk-BCX5THDQ.js.map +0 -1
  155. package/dist/chunk-BPYKWMI7.js +0 -194
  156. package/dist/chunk-BPYKWMI7.js.map +0 -1
  157. package/dist/chunk-CZGYJKG6.js +0 -421
  158. package/dist/chunk-CZGYJKG6.js.map +0 -1
  159. package/dist/chunk-D2UH4CFE.cjs +0 -120
  160. package/dist/chunk-D2UH4CFE.cjs.map +0 -1
  161. package/dist/chunk-DAU3HNEG.js +0 -344
  162. package/dist/chunk-DAU3HNEG.js.map +0 -1
  163. package/dist/chunk-H2V4JMSA.js +0 -219
  164. package/dist/chunk-H2V4JMSA.js.map +0 -1
  165. package/dist/chunk-I2276W3I.cjs +0 -66
  166. package/dist/chunk-I2276W3I.cjs.map +0 -1
  167. package/dist/chunk-I35UYJJR.cjs +0 -468
  168. package/dist/chunk-I35UYJJR.cjs.map +0 -1
  169. package/dist/chunk-IU4DYUAV.js +0 -120
  170. package/dist/chunk-IU4DYUAV.js.map +0 -1
  171. package/dist/chunk-KXRWNFQJ.js +0 -51
  172. package/dist/chunk-KXRWNFQJ.js.map +0 -1
  173. package/dist/chunk-OP6PHB2U.js +0 -81
  174. package/dist/chunk-OP6PHB2U.js.map +0 -1
  175. package/dist/chunk-PXLEMUGJ.js +0 -165
  176. package/dist/chunk-PXLEMUGJ.js.map +0 -1
  177. package/dist/chunk-RCY6XLGU.cjs +0 -63
  178. package/dist/chunk-RCY6XLGU.cjs.map +0 -1
  179. package/dist/chunk-RHHVEJHJ.cjs +0 -1263
  180. package/dist/chunk-RHHVEJHJ.cjs.map +0 -1
  181. package/dist/chunk-TO3OQBXG.cjs +0 -165
  182. package/dist/chunk-TO3OQBXG.cjs.map +0 -1
  183. package/dist/chunk-V36ERY7Y.js +0 -375
  184. package/dist/chunk-V36ERY7Y.js.map +0 -1
  185. package/dist/chunk-VJM5XCB4.cjs +0 -614
  186. package/dist/chunk-VJM5XCB4.cjs.map +0 -1
  187. package/dist/chunk-VTS3PNMS.cjs +0 -344
  188. package/dist/chunk-VTS3PNMS.cjs.map +0 -1
  189. package/dist/chunk-WLODYNLB.js +0 -1067
  190. package/dist/chunk-WLODYNLB.js.map +0 -1
  191. package/dist/chunk-XKRJL2QT.cjs +0 -375
  192. package/dist/chunk-XKRJL2QT.cjs.map +0 -1
  193. package/dist/chunk-XWYZIFZW.js +0 -66
  194. package/dist/chunk-XWYZIFZW.js.map +0 -1
  195. package/dist/chunk-Y3NNQMAX.cjs +0 -194
  196. package/dist/chunk-Y3NNQMAX.cjs.map +0 -1
  197. package/dist/chunk-YE2T52VZ.cjs +0 -1067
  198. package/dist/chunk-YE2T52VZ.cjs.map +0 -1
  199. package/dist/chunk-Z26M4V2M.js +0 -468
  200. package/dist/chunk-Z26M4V2M.js.map +0 -1
  201. package/dist/chunk-Z45KX47P.cjs +0 -3897
  202. package/dist/chunk-Z45KX47P.cjs.map +0 -1
  203. package/dist/chunk-ZH4F5RMG.cjs +0 -403
  204. package/dist/chunk-ZH4F5RMG.cjs.map +0 -1
  205. package/dist/components/legend/index.cjs +0 -11
  206. package/dist/components/legend/index.cjs.map +0 -1
  207. package/dist/components/legend/index.css +0 -103
  208. package/dist/components/legend/index.css.map +0 -1
  209. package/dist/components/legend/index.d.cts +0 -37
  210. package/dist/components/legend/index.d.ts +0 -37
  211. package/dist/components/legend/index.js +0 -11
  212. package/dist/components/legend/index.js.map +0 -1
  213. package/dist/components/tooltip/index.cjs +0 -12
  214. package/dist/components/tooltip/index.cjs.map +0 -1
  215. package/dist/components/tooltip/index.css +0 -13
  216. package/dist/components/tooltip/index.css.map +0 -1
  217. package/dist/components/tooltip/index.d.cts +0 -71
  218. package/dist/components/tooltip/index.d.ts +0 -71
  219. package/dist/components/tooltip/index.js +0 -12
  220. package/dist/components/tooltip/index.js.map +0 -1
  221. package/dist/components/trend-indicator/index.cjs +0 -8
  222. package/dist/components/trend-indicator/index.cjs.map +0 -1
  223. package/dist/components/trend-indicator/index.css +0 -27
  224. package/dist/components/trend-indicator/index.css.map +0 -1
  225. package/dist/components/trend-indicator/index.d.cts +0 -44
  226. package/dist/components/trend-indicator/index.d.ts +0 -44
  227. package/dist/components/trend-indicator/index.js +0 -8
  228. package/dist/components/trend-indicator/index.js.map +0 -1
  229. package/dist/format-metric-value-MXm5DtQ_.d.cts +0 -24
  230. package/dist/format-metric-value-MXm5DtQ_.d.ts +0 -24
  231. package/dist/hooks/index.cjs +0 -31
  232. package/dist/hooks/index.cjs.map +0 -1
  233. package/dist/hooks/index.css +0 -103
  234. package/dist/hooks/index.css.map +0 -1
  235. package/dist/hooks/index.d.cts +0 -272
  236. package/dist/hooks/index.d.ts +0 -272
  237. package/dist/hooks/index.js +0 -31
  238. package/dist/hooks/index.js.map +0 -1
  239. package/dist/leaderboard-chart-BKYYXcg2.d.ts +0 -83
  240. package/dist/leaderboard-chart-DR7CGb0L.d.cts +0 -83
  241. package/dist/legend-C2grwnWk.d.cts +0 -9
  242. package/dist/legend-Cj0xM5dU.d.ts +0 -9
  243. package/dist/providers/index.cjs +0 -21
  244. package/dist/providers/index.cjs.map +0 -1
  245. package/dist/providers/index.css +0 -103
  246. package/dist/providers/index.css.map +0 -1
  247. package/dist/providers/index.d.cts +0 -28
  248. package/dist/providers/index.d.ts +0 -28
  249. package/dist/providers/index.js +0 -21
  250. package/dist/providers/index.js.map +0 -1
  251. package/dist/themes-BmVGrYnF.d.ts +0 -67
  252. package/dist/themes-CyjKm-P_.d.cts +0 -67
  253. package/dist/types-CuUEszrM.d.ts +0 -19
  254. package/dist/types-DZordNiO.d.cts +0 -505
  255. package/dist/types-DZordNiO.d.ts +0 -505
  256. package/dist/types-I67mddpr.d.cts +0 -78
  257. package/dist/types-I67mddpr.d.ts +0 -78
  258. package/dist/types-KtOPPzPX.d.cts +0 -19
  259. package/dist/utils/index.cjs +0 -44
  260. package/dist/utils/index.cjs.map +0 -1
  261. package/dist/utils/index.d.cts +0 -226
  262. package/dist/utils/index.d.ts +0 -226
  263. package/dist/utils/index.js +0 -44
  264. package/dist/utils/index.js.map +0 -1
  265. package/dist/with-responsive-CNfhzAUu.d.cts +0 -18
  266. package/dist/with-responsive-CNfhzAUu.d.ts +0 -18
  267. package/src/hooks/use-has-legend-child.ts +0 -22
@@ -26,10 +26,12 @@ describe( 'BarChart', () => {
26
26
  ],
27
27
  };
28
28
 
29
- const renderWithTheme = ( props = {} ) => {
29
+ const renderWithTheme = ( props = {}, children = undefined ) => {
30
30
  return render(
31
31
  <GlobalChartsProvider>
32
- <BarChart { ...defaultProps } { ...props } />
32
+ <BarChart { ...defaultProps } { ...props }>
33
+ { children }
34
+ </BarChart>
33
35
  </GlobalChartsProvider>
34
36
  );
35
37
  };
@@ -123,39 +125,54 @@ describe( 'BarChart', () => {
123
125
  } );
124
126
 
125
127
  describe( 'Legend', () => {
128
+ const multiSeriesData = [
129
+ {
130
+ label: 'Series A',
131
+ data: [ { date: new Date( '2024-01-01' ), value: 10, label: 'Jan 1' } ],
132
+ options: {},
133
+ },
134
+ {
135
+ label: 'Series B',
136
+ data: [ { date: new Date( '2024-01-01' ), value: 20, label: 'Jan 1' } ],
137
+ options: {},
138
+ },
139
+ ];
140
+
126
141
  test( 'shows legend when showLegend is true', () => {
127
- renderWithTheme( {
128
- showLegend: true,
129
- data: [
130
- {
131
- label: 'Series A',
132
- data: [ { date: new Date( '2024-01-01' ), value: 10, label: 'Jan 1' } ],
133
- options: {},
134
- },
135
- {
136
- label: 'Series B',
137
- data: [ { date: new Date( '2024-01-01' ), value: 20, label: 'Jan 1' } ],
138
- options: {},
139
- },
140
- ],
141
- } );
142
+ renderWithTheme( { showLegend: true, data: multiSeriesData } );
142
143
  expect( screen.getByText( 'Series A' ) ).toBeInTheDocument();
143
144
  expect( screen.getByText( 'Series B' ) ).toBeInTheDocument();
144
145
  } );
145
146
 
146
147
  test( 'hides legend when showLegend is false', () => {
147
- renderWithTheme( {
148
- showLegend: false,
149
- data: [
150
- {
151
- label: 'Series A',
152
- data: [ { date: new Date( '2024-01-01' ), value: 10, label: 'Jan 1' } ],
153
- options: {},
154
- },
155
- ],
156
- } );
148
+ renderWithTheme( { showLegend: false, data: multiSeriesData } );
157
149
  expect( screen.queryByText( 'Series A' ) ).not.toBeInTheDocument();
158
150
  } );
151
+
152
+ test( 'renders composition legend as child component', () => {
153
+ renderWithTheme( { data: multiSeriesData }, <BarChart.Legend /> );
154
+
155
+ expect( screen.getAllByTestId( 'legend-item' ) ).toHaveLength( 2 );
156
+ expect( screen.getByText( 'Series A' ) ).toBeInTheDocument();
157
+ expect( screen.getByText( 'Series B' ) ).toBeInTheDocument();
158
+ } );
159
+
160
+ test( 'renders composition legend regardless of showLegend value', () => {
161
+ renderWithTheme( { data: multiSeriesData, showLegend: false }, <BarChart.Legend /> );
162
+
163
+ expect( screen.getAllByTestId( 'legend-item' ) ).toHaveLength( 2 );
164
+ } );
165
+
166
+ test( 'renders composition legend in top position', () => {
167
+ renderWithTheme( { data: multiSeriesData }, <BarChart.Legend position="top" /> );
168
+
169
+ // Legend should appear before the chart content in DOM order
170
+ expect( screen.getAllByTestId( 'legend-item' ) ).toHaveLength( 2 );
171
+ const html = document.body.innerHTML;
172
+ expect( html.indexOf( 'data-testid="legend-horizontal"' ) ).toBeLessThan(
173
+ html.indexOf( 'role="grid"' )
174
+ );
175
+ } );
159
176
  } );
160
177
 
161
178
  describe( 'Grid Visibility', () => {
@@ -743,7 +760,7 @@ describe( 'BarChart', () => {
743
760
 
744
761
  renderWithTheme( {
745
762
  showLegend: true,
746
- legendInteractive: true,
763
+ legend: { interactive: true },
747
764
  chartId: 'test-interactive-bar-chart',
748
765
  data: [
749
766
  {
@@ -771,7 +788,7 @@ describe( 'BarChart', () => {
771
788
  it( 'does not filter series when legendInteractive is false', () => {
772
789
  renderWithTheme( {
773
790
  showLegend: true,
774
- legendInteractive: false,
791
+ legend: { interactive: false },
775
792
  chartId: 'test-non-interactive-bar-chart',
776
793
  data: [
777
794
  {
@@ -795,7 +812,7 @@ describe( 'BarChart', () => {
795
812
  it( 'shows all series when chartId is missing even if legendInteractive is true', () => {
796
813
  renderWithTheme( {
797
814
  showLegend: true,
798
- legendInteractive: true,
815
+ legend: { interactive: true },
799
816
  // No chartId provided
800
817
  data: [
801
818
  {
@@ -823,7 +840,7 @@ describe( 'BarChart', () => {
823
840
 
824
841
  renderWithTheme( {
825
842
  showLegend: true,
826
- legendInteractive: true,
843
+ legend: { interactive: true },
827
844
  chartId: 'test-all-hidden-bar-chart',
828
845
  data: [
829
846
  {
@@ -17,6 +17,7 @@ import {
17
17
  } from '../../providers';
18
18
  import { formatMetricValue, attachSubComponents } from '../../utils';
19
19
  import { useChartChildren } from '../private/chart-composition';
20
+ import { ChartLayout } from '../private/chart-layout';
20
21
  import { SingleChartContext } from '../private/single-chart-context';
21
22
  import { withResponsive } from '../private/with-responsive';
22
23
  import { useLeaderboardLegendItems } from './hooks';
@@ -110,31 +111,26 @@ const BarWithLabel = ( {
110
111
  * LeaderboardChart component displays a ranked list of data with progress bars
111
112
  * and optional comparison values.
112
113
  *
113
- * @param props - Component props
114
- * @param props.data - Array of leaderboard entries to display
115
- * @param props.chartId - Optional unique identifier for the chart
116
- * @param props.width - Optional width of the chart container in pixels
117
- * @param props.height - Optional height of the chart container in pixels
118
- * @param props.withComparison - Whether to show comparison data
119
- * @param props.withOverlayLabel - Whether to overlay the label on top of the bar
120
- * @param props.primaryColor - Primary color for current period bars
121
- * @param props.secondaryColor - Secondary color for comparison period bars
122
- * @param props.valueFormatter - Custom formatter for values
123
- * @param props.deltaFormatter - Custom formatter for delta values
124
- * @param props.loading - Whether the chart is in loading state
125
- * @param props.animation - Whether the chart should animate on load
126
- * @param props.showLegend - Whether to show legend
127
- * @param props.legendOrientation - Legend orientation
128
- * @param props.legendPosition - Legend position
129
- * @param props.legendAlignment - Legend alignment
130
- * @param props.legendShape - Legend shape
131
- * @param props.legendShapeStyles - Styles for legend shapes (width, height, margin)
132
- * @param props.legendLabels - Custom labels for legend items
133
- * @param props.legendInteractive - Whether legend items are interactive (clickable to toggle series visibility)
134
- * @param props.gap - Spacing between legend and chart content
135
- * @param props.children - Child components for composition API
136
- * @param props.className - Additional CSS class name
137
- * @param props.style - Custom styling for the chart container
114
+ * @param props - Component props
115
+ * @param props.data - Array of leaderboard entries to display
116
+ * @param props.chartId - Optional unique identifier for the chart
117
+ * @param props.width - Optional width of the chart container in pixels
118
+ * @param props.height - Optional height of the chart container in pixels
119
+ * @param props.withComparison - Whether to show comparison data
120
+ * @param props.withOverlayLabel - Whether to overlay the label on top of the bar
121
+ * @param props.primaryColor - Primary color for current period bars
122
+ * @param props.secondaryColor - Secondary color for comparison period bars
123
+ * @param props.valueFormatter - Custom formatter for values
124
+ * @param props.deltaFormatter - Custom formatter for delta values
125
+ * @param props.loading - Whether the chart is in loading state
126
+ * @param props.animation - Whether the chart should animate on load
127
+ * @param props.showLegend - Whether to show legend
128
+ * @param props.legend - Legend configuration (orientation, position, alignment, shape, shapeStyles, interactive)
129
+ * @param props.legendLabels - Custom labels for legend items
130
+ * @param props.gap - Spacing between legend and chart content
131
+ * @param props.children - Child components for composition API
132
+ * @param props.className - Additional CSS class name
133
+ * @param props.style - Custom styling for the chart container
138
134
  * @return JSX element representing the leaderboard chart
139
135
  */
140
136
  const LeaderboardChartInternal: FC< LeaderboardChartProps > = ( {
@@ -151,24 +147,22 @@ const LeaderboardChartInternal: FC< LeaderboardChartProps > = ( {
151
147
  animation,
152
148
  loading = false,
153
149
  showLegend = false,
154
- legendOrientation = 'horizontal',
155
- legendPosition = 'bottom',
156
- legendAlignment = 'center',
157
- legendShape = 'circle',
158
- legendShapeStyles: legendShapeStylesProp,
150
+ legend = {},
159
151
  legendLabels,
160
- legendInteractive = false,
161
152
  gap = 'md',
162
153
  className,
163
154
  style,
164
155
  children,
165
156
  } ) => {
157
+ const legendInteractive = legend.interactive ?? false;
158
+ const legendPosition = legend.position ?? 'bottom';
159
+
166
160
  const chartId = useChartId( providedChartId );
167
161
  const { leaderboardChart: leaderboardChartSettings } = useGlobalChartsTheme();
168
- const legendShapeStyles = { width: 8, height: 8, ...legendShapeStylesProp };
162
+ const legendShapeStyles = { width: 8, height: 8, ...legend.shapeStyles };
169
163
 
170
164
  // Process children to extract compound components
171
- const { otherChildren } = useChartChildren( children, 'LeaderboardChart' );
165
+ const { legendChildren, nonLegendChildren } = useChartChildren( children, 'LeaderboardChart' );
172
166
  const {
173
167
  labelSpacing,
174
168
  rowGap,
@@ -253,47 +247,43 @@ const LeaderboardChartInternal: FC< LeaderboardChartProps > = ( {
253
247
  // Handle empty or undefined data
254
248
  if ( ! data || data.length === 0 ) {
255
249
  return (
256
- <SingleChartContext.Provider
257
- value={ {
258
- chartId,
259
- chartWidth: 0, // LeaderboardChart doesn't need specific dimensions
260
- chartHeight: 0,
261
- } }
262
- >
263
- <Stack
264
- direction="column"
265
- data-testid="leaderboard-chart-container"
250
+ <SingleChartContext.Provider value={ { chartId } }>
251
+ <ChartLayout
252
+ legendPosition={ legendPosition }
253
+ legendElement={ false }
254
+ legendChildren={ legendChildren }
266
255
  className={ clsx(
267
256
  styles.leaderboardChart,
268
- { [ styles[ 'leaderboardChart--responsive' ] ]: ! propWidth && ! propHeight },
269
- { [ styles[ 'leaderboardChart--loading' ] ]: loading },
257
+ {
258
+ [ styles[ 'leaderboardChart--responsive' ] ]: ! propWidth && ! propHeight,
259
+ [ styles[ 'leaderboardChart--loading' ] ]: loading,
260
+ },
270
261
  className
271
262
  ) }
272
263
  gap={ gap }
273
- style={ {
274
- ...style,
275
- width: propWidth || undefined,
276
- height: propHeight || undefined,
277
- } }
264
+ style={ { ...style, width: propWidth || undefined, height: propHeight || undefined } }
265
+ data-testid="leaderboard-chart-container"
266
+ trailingContent={ nonLegendChildren }
278
267
  >
279
268
  <div className={ styles.emptyState }>
280
269
  { loading
281
270
  ? __( 'Loading…', 'jetpack-charts' )
282
271
  : __( 'No data available', 'jetpack-charts' ) }
283
272
  </div>
284
- { /* Render children from composition API */ }
285
- { otherChildren }
286
- </Stack>
273
+ </ChartLayout>
287
274
  </SingleChartContext.Provider>
288
275
  );
289
276
  }
290
277
 
291
278
  const legendElement = showLegend && (
292
279
  <Legend
293
- orientation={ legendOrientation }
280
+ orientation={ legend.orientation ?? 'horizontal' }
294
281
  position={ legendPosition }
295
- alignment={ legendAlignment }
296
- shape={ legendShape }
282
+ alignment={ legend.alignment ?? 'center' }
283
+ labelStyles={ legend.labelStyles }
284
+ itemClassName={ legend.itemClassName }
285
+ itemStyles={ legend.itemStyles }
286
+ shape={ legend.shape ?? 'circle' }
297
287
  shapeStyles={ legendShapeStyles }
298
288
  chartId={ chartId }
299
289
  interactive={ legendInteractive }
@@ -301,16 +291,11 @@ const LeaderboardChartInternal: FC< LeaderboardChartProps > = ( {
301
291
  );
302
292
 
303
293
  return (
304
- <SingleChartContext.Provider
305
- value={ {
306
- chartId,
307
- chartWidth: 0, // LeaderboardChart doesn't need specific dimensions
308
- chartHeight: 0,
309
- } }
310
- >
311
- <Stack
312
- direction="column"
313
- data-testid="leaderboard-chart-container"
294
+ <SingleChartContext.Provider value={ { chartId } }>
295
+ <ChartLayout
296
+ legendPosition={ legendPosition }
297
+ legendElement={ legendElement }
298
+ legendChildren={ legendChildren }
314
299
  className={ clsx(
315
300
  styles.leaderboardChart,
316
301
  {
@@ -325,9 +310,9 @@ const LeaderboardChartInternal: FC< LeaderboardChartProps > = ( {
325
310
  width: propWidth || undefined,
326
311
  height: propHeight || undefined,
327
312
  } }
313
+ data-testid="leaderboard-chart-container"
314
+ trailingContent={ nonLegendChildren }
328
315
  >
329
- { legendPosition === 'top' && legendElement }
330
-
331
316
  <div className={ styles.leaderboardChart__content }>
332
317
  { allSeriesHidden ? (
333
318
  <div className={ styles.emptyState }>
@@ -375,12 +360,7 @@ const LeaderboardChartInternal: FC< LeaderboardChartProps > = ( {
375
360
  </Grid>
376
361
  ) }
377
362
  </div>
378
-
379
- { legendPosition === 'bottom' && legendElement }
380
-
381
- { /* Render children from composition API */ }
382
- { otherChildren }
383
- </Stack>
363
+ </ChartLayout>
384
364
  </SingleChartContext.Provider>
385
365
  );
386
366
  };
@@ -169,8 +169,7 @@ describe( 'LeaderboardChart', () => {
169
169
  data={ mockData }
170
170
  withComparison={ true }
171
171
  showLegend={ true }
172
- legendShape="rect"
173
- legendShapeStyles={ { width: 10, height: 6 } }
172
+ legend={ { shape: 'rect', shapeStyles: { width: 10, height: 6 } } }
174
173
  />
175
174
  );
176
175
 
@@ -306,7 +305,7 @@ describe( 'LeaderboardChart', () => {
306
305
  data={ mockData }
307
306
  withComparison={ true }
308
307
  showLegend={ true }
309
- legendInteractive={ true }
308
+ legend={ { interactive: true } }
310
309
  />
311
310
  );
312
311
 
@@ -320,7 +319,7 @@ describe( 'LeaderboardChart', () => {
320
319
  data={ mockData }
321
320
  withComparison={ true }
322
321
  showLegend={ true }
323
- legendInteractive={ false }
322
+ legend={ { interactive: false } }
324
323
  />
325
324
  );
326
325
 
@@ -335,7 +334,7 @@ describe( 'LeaderboardChart', () => {
335
334
  data={ mockData }
336
335
  withComparison={ true }
337
336
  showLegend={ true }
338
- legendInteractive={ true }
337
+ legend={ { interactive: true } }
339
338
  />
340
339
  );
341
340
 
@@ -1,6 +1,5 @@
1
1
  import { type ReactNode } from 'react';
2
2
  import { BaseChartProps, LeaderboardEntry } from '../../types';
3
- import type { LegendShapeStyles } from '../../components/legend';
4
3
 
5
4
  export interface LeaderboardChartProps
6
5
  extends Pick<
@@ -8,16 +7,12 @@ export interface LeaderboardChartProps
8
7
  | 'className'
9
8
  | 'data'
10
9
  | 'showLegend'
11
- | 'legendOrientation'
12
- | 'legendPosition'
13
- | 'legendAlignment'
14
- | 'legendShape'
10
+ | 'legend'
15
11
  | 'chartId'
16
12
  | 'width'
17
13
  | 'height'
18
14
  | 'size'
19
15
  | 'gap'
20
- | 'legendInteractive'
21
16
  | 'animation'
22
17
  > {
23
18
  /**
@@ -62,11 +57,6 @@ export interface LeaderboardChartProps
62
57
  '--a8c--charts--leaderboard--bar--border-radius'?: string;
63
58
  };
64
59
 
65
- /**
66
- * Styles for legend shapes (width, height, margin).
67
- */
68
- legendShapeStyles?: LegendShapeStyles;
69
-
70
60
  /**
71
61
  * Custom labels for legend items
72
62
  */
@@ -1,11 +1,6 @@
1
1
  .line-chart {
2
2
  position: relative;
3
3
 
4
- &__svg-wrapper {
5
- flex: 1;
6
- min-height: 0; // Required for flex shrinking
7
- }
8
-
9
4
  &--animated {
10
5
 
11
6
  path {