Charts: Extract shared ChartLayout component for chart+legend layout#47554
Conversation
Introduces a ChartLayout component that encapsulates the shared Stack + legend positioning pattern used by all chart components. Includes comprehensive tests covering legend positioning, visibility management, ref forwarding, and composition API slot rendering. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Are you an Automattician? Please test your changes on all WordPress.com environments to help mitigate accidental explosions.
Interested in more tips and information?
|
|
Thank you for your PR! When contributing to Jetpack, we have a few suggestions that can help us test and review your patch:
This comment will be updated as you work on your PR and make changes. If you think that some of those checks are not needed for your PR, please explain why you think so. Thanks for cooperation 🤖 Follow this PR Review Process:
If you have questions about anything, reach out in #jetpack-developers for guidance! |
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…state Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Code Coverage SummaryCoverage changed in 2 files.
2 files are newly checked for coverage.
|
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
ChartLayout now accepts children as either ReactNode or a render prop receiving ContentMeasurements (contentRef, contentWidth, contentHeight, isMeasured). This allows charts to consolidate their useElementSize calls into the shared layout component. Adds waitForMeasurement prop that hides the layout until content is measured. The legacy isWaitingForMeasurement prop is preserved as deprecated for backward compatibility. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move SingleChartContext.Provider to wrap ChartLayout (outside) so composition API legends in legend slots can access the chart context. Keep inner provider inside the render prop for measured dimensions. Remove waitForMeasurement since PieChart uses fallback dimensions (300x300) when unmeasured and doesn't need to hide. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…t render prop Remove individual useElementSize calls from each chart and use ChartLayout's render prop to receive content measurements instead. Pattern: outer SingleChartContext.Provider with fallback dimensions (for composition API legends in layout slots), inner provider inside render prop with measured dimensions (for chart internals). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
564414e to
216809c
Compare
Replace the expectDomOrder helper with direct compareDocumentPosition assertions. The helper required eslint-disable no-bitwise for the masking check; inline expect(...).toBe(Node.DOCUMENT_POSITION_FOLLOWING) is clearer and avoids the suppression. Made-with: Cursor
ChartLayout now wraps render prop children in a content div with shared flex styles (flex: 1; min-height: 0; min-width: 0), removing duplicated svg-wrapper rules from line-chart, bar-chart, pie-chart, and pie-semi-circle-chart SCSS modules. Charts no longer need to attach contentRef — ChartLayout handles measurement internally via the content wrapper div. Replace custom flex centering CSS in pie-chart and pie-semi-circle-chart svg-wrappers with Stack components using align/justify props, per project guidelines preferring layout primitives over ad-hoc flex rules. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…arts
The pie-chart class had `display: flex; flex-direction: column;
align-items: center` which duplicated what the Stack component
already provides. The `align-items: center` also prevented the
chart-layout content area from stretching to full width.
Similarly, the centering Stack inside both pie charts had inline
`style={{ width: '100%', height: '100%' }}` that is unnecessary
now that ChartLayout's content wrapper handles flex sizing.
Also removes hardcoded `size` props from two pie chart stories
that were masking the responsive sizing behavior.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
3e7180a to
9496b45
Compare
Superpowers directories are now ignored via .git/info/exclude. Removing the previously tracked plan file so it stays local. Made-with: Cursor
|
@claude again please |
|
Claude finished @adamwoodnz's task in 1m 39s —— View job Code Review
All issues from the previous two reviews have been cleanly resolved. Here's the current state: Resolved ✅
BarChart PieChart/PieSemiCircleChart zero-dimensions context — Cleanly resolved by making Pie chart centering fill behavior — Fixed via CSS class ( Tests — All 12 tests are well-targeted. The render-prop measurement test, visibility-hidden test, and plain-ReactNode no-hiding test cover the critical behaviors introduced by ChartLayout. Remaining minor observations (non-blocking)
Two What's working wellThe refactor is solid end-to-end. The render-prop measurement pattern via This looks ready to merge. 🟢 |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 19 out of 19 changed files in this pull request and generated 4 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
projects/js-packages/charts/src/charts/private/chart-layout/chart-layout.tsx
Outdated
Show resolved
Hide resolved
projects/js-packages/charts/src/charts/private/chart-layout/test/chart-layout.test.tsx
Show resolved
Hide resolved
The setup-element-size-mock.js introduced in e242b7d had three flaws: 1. Accessed Element.prototype unconditionally, crashing the SSR test (jest-environment node) with "Element is not defined" 2. Returned hardcoded 800×400 for ALL zero-sized elements, overriding explicit dimensions (e.g. Sparkline at 100×40 got height 400) 3. The visx tooltip mock removed in 92201ce was NOT redundant — react-use-measure has a mounted.current guard that prevents state updates when ResizeObserver fires synchronously during the ref callback, leaving containerBounds at {0,0,...} and suppressing all tooltips Fixes: - Guard Element access with typeof check for SSR compatibility - Walk up DOM tree for ancestor inline style dimensions instead of hardcoded fallback, so charts with explicit sizes measure correctly - Restore visx tooltip mock with updated documentation explaining the react-use-measure timing issue - Override getBoundingClientRect locally in chart-layout visibility test to simulate unmeasured state Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When children is a plain ReactNode, the content measurement ref is never attached, so contentHeight stays 0. Firing onContentHeightChange(0) in that case is misleading for callers who wire up the callback without realizing it only produces meaningful values in render-prop mode. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 19 out of 19 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
projects/js-packages/charts/src/charts/private/chart-layout/test/chart-layout.test.tsx
Outdated
Show resolved
Hide resolved
Ensures the global getBoundingClientRect mock is restored even if an assertion throws, preventing leak into subsequent tests. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 19 out of 19 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
projects/js-packages/charts/src/charts/private/chart-layout/chart-layout.tsx
Outdated
Show resolved
Hide resolved
projects/js-packages/charts/src/charts/private/chart-layout/chart-layout.tsx
Show resolved
Hide resolved
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
c082a9e to
3539809
Compare
| chartRef?: React.RefObject< ChartInstanceRef >; | ||
| chartWidth: number; | ||
| chartHeight: number; | ||
| chartWidth?: number; |
There was a problem hiding this comment.
Making chartWidth/chartHeight optional changes the contract for context consumers. line-chart-annotations-overlay.tsx reads both values and passes them directly to SVG width/height attributes and DataContext — undefined would break the overlay.
Would it be better to add an early return guard in the overlay, consistent with the existing ones?
if ( ! chartWidth || ! chartHeight ) {
return null;
}
| { renderedChildren } | ||
| </div> | ||
| ) : ( | ||
| renderedChildren |
There was a problem hiding this comment.
Would it make sense to align the element structure here with the case where children is a function? Are there any special considerations I might have missed?
There was a problem hiding this comment.
The wrapping <div ref={contentRef}> exists solely to attach the useElementSize ref for measurement — render-prop consumers need measured contentWidth/contentHeight to size their charts. Non-render-prop children (e.g. leaderboard) don't need measurement, so the extra wrapper would just add an unnecessary DOM node and could interfere with layout/styling (the wrapper has flex: 1; min-height: 0; min-width: 0 rules that assume it's sizing chart content).
| legendElement={ legendElement } | ||
| legendChildren={ legendChildren } |
There was a problem hiding this comment.
What happens if both passed?
There was a problem hiding this comment.
kangzj
left a comment
There was a problem hiding this comment.
Smoked tested all work well. Leave two comments no blockers. Thanks for working on this. Looks so much cleaner 👍
I have seen these tick tests fail locally but they are passing for me currently, and in CI 🤷 |
…erlay Updated the early return condition to include checks for chartWidth and chartHeight, ensuring that the component only renders when all necessary dimensions are available. This prevents potential rendering issues when the chart is not properly sized.
Composition legend stories for bar, pie, donut, and semi-circle were cherry-picking individual props instead of spreading args. This meant Storybook controls like showLegend had no effect, preventing dual legend rendering (showLegend + composition <Chart.Legend>) that works in line chart and leaderboard stories. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>


Introduces a
ChartLayoutcomponent that encapsulates the shared Stack + legend positioning pattern duplicated across all 5 chart types. ChartLayout supports a render prop pattern with built-inuseElementSizemeasurement, eliminating duplicated resize logic from individual charts.Fixes CHARTS-181: Extract shared ChartLayout component for chart + legend layout pattern
Proposed changes
ChartLayoutcomponent wrappingStack direction="column"with legend slot rendering (top/bottom), visibility management, and optional render prop measurement viauseElementSizechildren: ReactNode | (measurements => ReactNode)) so charts that need measurement (line, bar, pie) getcontentWidth/contentHeightfrom a shared content wrapper divSingleChartContext.Providerper chart — previously some charts had redundant inner providersflex: 1; min-height: 0; min-width: 0) intochart-layout.module.scss, removing duplicatedsvg-wrapperblocks from 4 chart SCSS filesStackcomponent for pie chart SVG centering instead of custom flex CSS (per project guidelines)sizeprops from pie chart stories that were masking responsive sizing behaviorOther information
Related product discussion/links
Does this pull request change what data or activity we track or use?
No
Testing instructions
Run the chart tests to verify all pass:
Expected: 792 tests pass across 42 suites.
Start Storybook and verify each chart type renders correctly:
sizeprop — should fill container), with composition legend, and donut variantsFor pie charts specifically, verify responsive sizing:
<PieChart.Legend>composition child — chart should still resize correctlylegendPositionbetween top/bottom — layout should adjust without breaking sizingVerify legend positioning works for all charts:
legendPosition: 'top'andlegendPosition: 'bottom'in controls