@automattic/charts 0.56.7 → 0.57.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/AGENTS.md +28 -98
- package/CHANGELOG.md +16 -0
- package/dist/charts/bar-chart/index.cjs +5 -6
- package/dist/charts/bar-chart/index.cjs.map +1 -1
- package/dist/charts/bar-chart/index.d.cts +3 -3
- package/dist/charts/bar-chart/index.d.ts +3 -3
- package/dist/charts/bar-chart/index.js +4 -5
- package/dist/charts/bar-list-chart/index.cjs +6 -7
- package/dist/charts/bar-list-chart/index.cjs.map +1 -1
- package/dist/charts/bar-list-chart/index.d.cts +3 -3
- package/dist/charts/bar-list-chart/index.d.ts +3 -3
- package/dist/charts/bar-list-chart/index.js +5 -6
- package/dist/charts/conversion-funnel-chart/index.cjs +5 -6
- package/dist/charts/conversion-funnel-chart/index.cjs.map +1 -1
- package/dist/charts/conversion-funnel-chart/index.d.cts +1 -1
- package/dist/charts/conversion-funnel-chart/index.d.ts +1 -1
- package/dist/charts/conversion-funnel-chart/index.js +4 -5
- package/dist/charts/geo-chart/index.cjs +4 -4
- package/dist/charts/geo-chart/index.d.cts +1 -1
- package/dist/charts/geo-chart/index.d.ts +1 -1
- package/dist/charts/geo-chart/index.js +3 -3
- package/dist/charts/leaderboard-chart/index.cjs +5 -5
- package/dist/charts/leaderboard-chart/index.css +8 -9
- package/dist/charts/leaderboard-chart/index.css.map +1 -1
- package/dist/charts/leaderboard-chart/index.d.cts +3 -3
- package/dist/charts/leaderboard-chart/index.d.ts +3 -3
- package/dist/charts/leaderboard-chart/index.js +4 -4
- package/dist/charts/line-chart/index.cjs +5 -6
- package/dist/charts/line-chart/index.cjs.map +1 -1
- package/dist/charts/line-chart/index.d.cts +3 -3
- package/dist/charts/line-chart/index.d.ts +3 -3
- package/dist/charts/line-chart/index.js +4 -5
- package/dist/charts/pie-chart/index.cjs +5 -6
- package/dist/charts/pie-chart/index.cjs.map +1 -1
- package/dist/charts/pie-chart/index.d.cts +4 -4
- package/dist/charts/pie-chart/index.d.ts +4 -4
- package/dist/charts/pie-chart/index.js +4 -5
- package/dist/charts/pie-semi-circle-chart/index.cjs +5 -6
- package/dist/charts/pie-semi-circle-chart/index.cjs.map +1 -1
- package/dist/charts/pie-semi-circle-chart/index.d.cts +4 -4
- package/dist/charts/pie-semi-circle-chart/index.d.ts +4 -4
- package/dist/charts/pie-semi-circle-chart/index.js +4 -5
- package/dist/charts/sparkline/index.cjs +6 -7
- package/dist/charts/sparkline/index.cjs.map +1 -1
- package/dist/charts/sparkline/index.js +5 -6
- package/dist/{chunk-XD2HV7M5.js → chunk-2NCY7R4G.js} +127 -762
- package/dist/chunk-2NCY7R4G.js.map +1 -0
- package/dist/{chunk-RFSHE3HL.js → chunk-32DH6JDF.js} +64 -43
- package/dist/chunk-32DH6JDF.js.map +1 -0
- package/dist/{chunk-SSFFCBCF.js → chunk-4OPFE4RM.js} +11 -8
- package/dist/chunk-4OPFE4RM.js.map +1 -0
- package/dist/{chunk-CAFJRZPZ.cjs → chunk-77OKCVQN.cjs} +17 -17
- package/dist/{chunk-CAFJRZPZ.cjs.map → chunk-77OKCVQN.cjs.map} +1 -1
- package/dist/{chunk-K6TGILHX.cjs → chunk-7FQX4ALL.cjs} +6 -6
- package/dist/{chunk-K6TGILHX.cjs.map → chunk-7FQX4ALL.cjs.map} +1 -1
- package/dist/{chunk-7FDQGBY7.js → chunk-BCX5THDQ.js} +9 -7
- package/dist/chunk-BCX5THDQ.js.map +1 -0
- package/dist/{chunk-KHQPN77E.js → chunk-CZGYJKG6.js} +4 -4
- package/dist/{chunk-3EXJP67N.cjs → chunk-D2UH4CFE.cjs} +9 -9
- package/dist/{chunk-3EXJP67N.cjs.map → chunk-D2UH4CFE.cjs.map} +1 -1
- package/dist/{chunk-TE63Y5PX.js → chunk-DAU3HNEG.js} +2 -2
- package/dist/chunk-DAU3HNEG.js.map +1 -0
- package/dist/{chunk-MDRCAGKZ.js → chunk-H2V4JMSA.js} +3 -3
- package/dist/{chunk-UFRBUT2D.cjs → chunk-I35UYJJR.cjs} +49 -6
- package/dist/chunk-I35UYJJR.cjs.map +1 -0
- package/dist/{chunk-GWBS65VC.js → chunk-IU4DYUAV.js} +3 -3
- package/dist/{chunk-E62LCBGD.js → chunk-PXLEMUGJ.js} +3 -3
- package/dist/{chunk-YDVHT7GS.cjs → chunk-RHHVEJHJ.cjs} +83 -62
- package/dist/chunk-RHHVEJHJ.cjs.map +1 -0
- package/dist/{chunk-YAXY5L7I.cjs → chunk-TO3OQBXG.cjs} +5 -5
- package/dist/{chunk-YAXY5L7I.cjs.map → chunk-TO3OQBXG.cjs.map} +1 -1
- package/dist/{chunk-VPAEBI2F.js → chunk-V36ERY7Y.js} +9 -7
- package/dist/chunk-V36ERY7Y.js.map +1 -0
- package/dist/{chunk-X7JL2NYJ.cjs → chunk-VJM5XCB4.cjs} +33 -30
- package/dist/chunk-VJM5XCB4.cjs.map +1 -0
- package/dist/{chunk-ZVGEDXDP.cjs → chunk-VTS3PNMS.cjs} +2 -2
- package/dist/{chunk-ZVGEDXDP.cjs.map → chunk-VTS3PNMS.cjs.map} +1 -1
- package/dist/{chunk-OMS5QIJN.js → chunk-WLODYNLB.js} +9 -7
- package/dist/chunk-WLODYNLB.js.map +1 -0
- package/dist/{chunk-NQJE2CC7.cjs → chunk-XKRJL2QT.cjs} +25 -23
- package/dist/chunk-XKRJL2QT.cjs.map +1 -0
- package/dist/{chunk-O2JIANHK.cjs → chunk-YE2T52VZ.cjs} +33 -31
- package/dist/chunk-YE2T52VZ.cjs.map +1 -0
- package/dist/{chunk-IS5YYLTV.js → chunk-Z26M4V2M.js} +46 -3
- package/dist/chunk-Z26M4V2M.js.map +1 -0
- package/dist/{chunk-55ZCOYDF.cjs → chunk-Z45KX47P.cjs} +153 -788
- package/dist/chunk-Z45KX47P.cjs.map +1 -0
- package/dist/{chunk-BXFD7JIG.cjs → chunk-ZH4F5RMG.cjs} +26 -24
- package/dist/chunk-ZH4F5RMG.cjs.map +1 -0
- package/dist/components/legend/index.cjs +3 -3
- package/dist/components/legend/index.d.cts +4 -4
- package/dist/components/legend/index.d.ts +4 -4
- package/dist/components/legend/index.js +2 -2
- package/dist/components/tooltip/index.d.cts +1 -1
- package/dist/components/tooltip/index.d.ts +1 -1
- package/dist/hooks/index.cjs +3 -3
- package/dist/hooks/index.d.cts +7 -3
- package/dist/hooks/index.d.ts +7 -3
- package/dist/hooks/index.js +2 -2
- package/dist/index.cjs +13 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +8 -9
- package/dist/index.css.map +1 -1
- package/dist/index.d.cts +7 -7
- package/dist/index.d.ts +7 -7
- package/dist/index.js +12 -13
- package/dist/{leaderboard-chart-BSgEw_Um.d.ts → leaderboard-chart-BKYYXcg2.d.ts} +5 -9
- package/dist/{leaderboard-chart-COtgamhe.d.cts → leaderboard-chart-DR7CGb0L.d.cts} +5 -9
- package/dist/{legend-C9ahiwOt.d.cts → legend-C2grwnWk.d.cts} +1 -1
- package/dist/{legend-jjMmhSg3.d.ts → legend-Cj0xM5dU.d.ts} +1 -1
- package/dist/providers/index.cjs +3 -3
- package/dist/providers/index.d.cts +3 -3
- package/dist/providers/index.d.ts +3 -3
- package/dist/providers/index.js +2 -2
- package/dist/{themes-DQzmaSze.d.ts → themes-BmVGrYnF.d.ts} +2 -2
- package/dist/{themes-CVR5rmIs.d.cts → themes-CyjKm-P_.d.cts} +2 -2
- package/dist/{types-DQNnq5Fr.d.ts → types-CuUEszrM.d.ts} +1 -1
- package/dist/{types-CzdN7rUe.d.cts → types-DZordNiO.d.cts} +11 -7
- package/dist/{types-CzdN7rUe.d.ts → types-DZordNiO.d.ts} +11 -7
- package/dist/types-I67mddpr.d.cts +78 -0
- package/dist/types-I67mddpr.d.ts +78 -0
- package/dist/{types-BBwg4Evw.d.cts → types-KtOPPzPX.d.cts} +1 -1
- package/dist/utils/index.cjs +2 -2
- package/dist/utils/index.d.cts +1 -1
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.js +1 -1
- package/package.json +6 -4
- package/src/charts/bar-chart/bar-chart.tsx +4 -3
- package/src/charts/bar-chart/test/bar-chart.test.tsx +30 -0
- package/src/charts/conversion-funnel-chart/test/conversion-funnel-chart.test.tsx +2 -2
- package/src/charts/leaderboard-chart/hooks/use-leaderboard-legend-items.ts +0 -2
- package/src/charts/leaderboard-chart/leaderboard-chart.module.scss +9 -10
- package/src/charts/leaderboard-chart/leaderboard-chart.tsx +95 -70
- package/src/charts/leaderboard-chart/test/leaderboard-chart.test.tsx +58 -29
- package/src/charts/leaderboard-chart/test/use-leaderboard-legend-items.test.tsx +2 -5
- package/src/charts/leaderboard-chart/types.ts +4 -7
- package/src/charts/line-chart/line-chart.tsx +2 -3
- package/src/charts/pie-chart/pie-chart.tsx +2 -3
- package/src/charts/pie-semi-circle-chart/pie-semi-circle-chart.tsx +2 -3
- package/src/components/legend/index.ts +8 -1
- package/src/components/legend/private/base-legend.tsx +32 -22
- package/src/components/legend/test/legend.test.tsx +148 -52
- package/src/components/legend/types.ts +42 -16
- package/src/hooks/test/use-zero-value-display.test.tsx +206 -0
- package/src/hooks/use-zero-value-display.ts +52 -23
- package/src/index.ts +7 -1
- package/src/providers/chart-context/test/chart-context.test.tsx +12 -6
- package/src/providers/chart-context/themes.ts +6 -4
- package/src/types.ts +11 -7
- package/src/utils/get-styles.ts +1 -1
- package/src/utils/test/get-styles.test.ts +12 -10
- package/dist/chunk-55ZCOYDF.cjs.map +0 -1
- package/dist/chunk-7FDQGBY7.js.map +0 -1
- package/dist/chunk-BXFD7JIG.cjs.map +0 -1
- package/dist/chunk-IS5YYLTV.js.map +0 -1
- package/dist/chunk-KNIMXN6Z.js +0 -51
- package/dist/chunk-KNIMXN6Z.js.map +0 -1
- package/dist/chunk-NQJE2CC7.cjs.map +0 -1
- package/dist/chunk-O2JIANHK.cjs.map +0 -1
- package/dist/chunk-OMS5QIJN.js.map +0 -1
- package/dist/chunk-RFSHE3HL.js.map +0 -1
- package/dist/chunk-SSFFCBCF.js.map +0 -1
- package/dist/chunk-SUDERBUA.cjs +0 -51
- package/dist/chunk-SUDERBUA.cjs.map +0 -1
- package/dist/chunk-TE63Y5PX.js.map +0 -1
- package/dist/chunk-UFRBUT2D.cjs.map +0 -1
- package/dist/chunk-VPAEBI2F.js.map +0 -1
- package/dist/chunk-X7JL2NYJ.cjs.map +0 -1
- package/dist/chunk-XD2HV7M5.js.map +0 -1
- package/dist/chunk-YDVHT7GS.cjs.map +0 -1
- package/dist/types-C05PdDJa.d.cts +0 -57
- package/dist/types-C05PdDJa.d.ts +0 -57
- /package/dist/{chunk-KHQPN77E.js.map → chunk-CZGYJKG6.js.map} +0 -0
- /package/dist/{chunk-MDRCAGKZ.js.map → chunk-H2V4JMSA.js.map} +0 -0
- /package/dist/{chunk-GWBS65VC.js.map → chunk-IU4DYUAV.js.map} +0 -0
- /package/dist/{chunk-E62LCBGD.js.map → chunk-PXLEMUGJ.js.map} +0 -0
|
@@ -69,30 +69,36 @@ export const BaseLegend: ForwardRefExoticComponent<
|
|
|
69
69
|
orientation = 'horizontal',
|
|
70
70
|
position = 'bottom',
|
|
71
71
|
alignment = 'center',
|
|
72
|
-
maxWidth,
|
|
73
|
-
textOverflow = 'wrap',
|
|
74
72
|
shape = 'rect',
|
|
75
73
|
fill = valueOrIdentityString,
|
|
76
74
|
size = valueOrIdentityString,
|
|
77
75
|
labelFormat = valueOrIdentity,
|
|
78
76
|
labelTransform = labelTransformFactory,
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
labelMargin = '0 4px',
|
|
85
|
-
itemMargin = '0',
|
|
86
|
-
itemDirection = 'row',
|
|
87
|
-
legendLabelProps,
|
|
88
|
-
legendItemClassName,
|
|
77
|
+
itemStyles,
|
|
78
|
+
itemClassName,
|
|
79
|
+
labelStyles,
|
|
80
|
+
labelClassName,
|
|
81
|
+
shapeStyles,
|
|
89
82
|
render,
|
|
90
83
|
interactive = false,
|
|
91
84
|
chartId,
|
|
92
|
-
...legendItemProps
|
|
93
85
|
},
|
|
94
86
|
ref
|
|
95
87
|
) => {
|
|
88
|
+
const { margin: itemMargin = '0', flexDirection: itemDirection = 'row' } = itemStyles ?? {};
|
|
89
|
+
const {
|
|
90
|
+
justifyContent: labelJustifyContent = 'flex-start',
|
|
91
|
+
flex: labelFlex = '0 0 auto',
|
|
92
|
+
margin: labelMargin = '0 4px',
|
|
93
|
+
maxWidth,
|
|
94
|
+
textOverflow = 'wrap',
|
|
95
|
+
} = labelStyles ?? {};
|
|
96
|
+
const {
|
|
97
|
+
width: shapeWidth = 16,
|
|
98
|
+
height: shapeHeight = 16,
|
|
99
|
+
margin: shapeMargin = '2px 4px 2px 0',
|
|
100
|
+
} = shapeStyles ?? {};
|
|
101
|
+
|
|
96
102
|
const theme = useGlobalChartsTheme();
|
|
97
103
|
const context = useContext( GlobalChartsContext );
|
|
98
104
|
|
|
@@ -176,13 +182,14 @@ export const BaseLegend: ForwardRefExoticComponent<
|
|
|
176
182
|
) }
|
|
177
183
|
style={ {
|
|
178
184
|
flexDirection: orientationToFlexDirection[ orientation ],
|
|
179
|
-
...theme.
|
|
185
|
+
...theme.legend?.containerStyles,
|
|
180
186
|
} }
|
|
181
187
|
>
|
|
182
188
|
{ labels.map( ( label, i ) => {
|
|
183
189
|
const visible = isSeriesVisible( label.text );
|
|
184
190
|
const handleClick = createClickHandler( label.text );
|
|
185
191
|
const handleKeyDown = createKeyDownHandler( label.text );
|
|
192
|
+
const matchedItem = items[ i ];
|
|
186
193
|
|
|
187
194
|
return (
|
|
188
195
|
<LegendItem
|
|
@@ -191,7 +198,7 @@ export const BaseLegend: ForwardRefExoticComponent<
|
|
|
191
198
|
styles[ 'legend-item' ],
|
|
192
199
|
interactive && styles[ 'legend-item--interactive' ],
|
|
193
200
|
! visible && styles[ 'legend-item--inactive' ],
|
|
194
|
-
|
|
201
|
+
itemClassName
|
|
195
202
|
) }
|
|
196
203
|
data-testid="legend-item"
|
|
197
204
|
key={ `legend-${ label.text }-${ i }` }
|
|
@@ -211,7 +218,6 @@ export const BaseLegend: ForwardRefExoticComponent<
|
|
|
211
218
|
? `${ label.text }: ${ visible ? 'visible' : 'hidden' }. Toggle visibility.`
|
|
212
219
|
: undefined
|
|
213
220
|
}
|
|
214
|
-
{ ...legendItemProps }
|
|
215
221
|
>
|
|
216
222
|
{ items[ i ]?.renderGlyph ? (
|
|
217
223
|
<svg
|
|
@@ -246,24 +252,28 @@ export const BaseLegend: ForwardRefExoticComponent<
|
|
|
246
252
|
/>
|
|
247
253
|
) }
|
|
248
254
|
<LegendLabel
|
|
249
|
-
|
|
255
|
+
data-testid="legend-label"
|
|
256
|
+
className={ clsx(
|
|
257
|
+
'visx-legend-label',
|
|
258
|
+
styles[ 'legend-item-label' ],
|
|
259
|
+
labelClassName
|
|
260
|
+
) }
|
|
250
261
|
style={ {
|
|
251
|
-
justifyContent:
|
|
262
|
+
justifyContent: labelJustifyContent,
|
|
252
263
|
flex: labelFlex,
|
|
253
264
|
margin: labelMargin,
|
|
254
|
-
...theme.
|
|
265
|
+
...theme.legend?.labelStyles,
|
|
255
266
|
} }
|
|
256
|
-
{ ...legendLabelProps }
|
|
257
267
|
>
|
|
258
268
|
<LegendText
|
|
259
269
|
text={ label.text }
|
|
260
270
|
textOverflow={ textOverflow }
|
|
261
271
|
maxWidth={ maxWidth }
|
|
262
272
|
/>
|
|
263
|
-
{
|
|
273
|
+
{ matchedItem?.value != null && matchedItem.value !== '' && (
|
|
264
274
|
<span className={ styles[ 'legend-item-value' ] }>
|
|
265
275
|
{ '\u00A0' }
|
|
266
|
-
{
|
|
276
|
+
{ matchedItem.value }
|
|
267
277
|
</span>
|
|
268
278
|
) }
|
|
269
279
|
</LegendLabel>
|
|
@@ -46,12 +46,12 @@ describe( 'BaseLegend', () => {
|
|
|
46
46
|
expect( legendItems ).toHaveLength( 0 );
|
|
47
47
|
} );
|
|
48
48
|
|
|
49
|
-
test( 'applies
|
|
49
|
+
test( 'applies itemClassName to legend items', () => {
|
|
50
50
|
render(
|
|
51
51
|
<BaseLegend
|
|
52
52
|
items={ defaultItems }
|
|
53
53
|
orientation="horizontal"
|
|
54
|
-
|
|
54
|
+
itemClassName="custom-legend-item"
|
|
55
55
|
/>
|
|
56
56
|
);
|
|
57
57
|
const legendItems = screen.getAllByTestId( 'legend-item' );
|
|
@@ -60,16 +60,65 @@ describe( 'BaseLegend', () => {
|
|
|
60
60
|
} );
|
|
61
61
|
} );
|
|
62
62
|
|
|
63
|
+
test( 'applies labelClassName to legend labels', () => {
|
|
64
|
+
render(
|
|
65
|
+
<BaseLegend
|
|
66
|
+
items={ defaultItems }
|
|
67
|
+
orientation="horizontal"
|
|
68
|
+
labelClassName="custom-legend-label"
|
|
69
|
+
/>
|
|
70
|
+
);
|
|
71
|
+
const labels = screen.getAllByTestId( 'legend-label' );
|
|
72
|
+
labels.forEach( label => {
|
|
73
|
+
expect( label ).toHaveClass( 'custom-legend-label' );
|
|
74
|
+
} );
|
|
75
|
+
} );
|
|
76
|
+
|
|
63
77
|
test( 'handles missing values', () => {
|
|
64
78
|
const itemsWithoutValues = [
|
|
65
|
-
{ label: 'Item 1', color: '#ff0000'
|
|
66
|
-
{ label: 'Item 2', color: '#00ff00'
|
|
79
|
+
{ label: 'Item 1', color: '#ff0000' },
|
|
80
|
+
{ label: 'Item 2', color: '#00ff00' },
|
|
67
81
|
];
|
|
68
82
|
render( <BaseLegend items={ itemsWithoutValues } orientation="horizontal" /> );
|
|
69
83
|
expect( screen.getByText( 'Item 1' ) ).toBeInTheDocument();
|
|
70
84
|
expect( screen.getByText( 'Item 2' ) ).toBeInTheDocument();
|
|
71
85
|
} );
|
|
72
86
|
|
|
87
|
+
test( 'does not render value span for empty string values', () => {
|
|
88
|
+
const itemsWithEmptyValues = [
|
|
89
|
+
{ label: 'Item 1', color: '#ff0000', value: '' },
|
|
90
|
+
{ label: 'Item 2', color: '#00ff00', value: '' },
|
|
91
|
+
];
|
|
92
|
+
render( <BaseLegend items={ itemsWithEmptyValues } orientation="horizontal" /> );
|
|
93
|
+
expect( screen.getByText( 'Item 1' ) ).toBeInTheDocument();
|
|
94
|
+
expect( screen.getByText( 'Item 2' ) ).toBeInTheDocument();
|
|
95
|
+
expect( screen.queryByText( '\u00A0' ) ).not.toBeInTheDocument();
|
|
96
|
+
} );
|
|
97
|
+
|
|
98
|
+
test( 'renders numeric value of 0 without hiding it', () => {
|
|
99
|
+
const itemsWithZeroValue = [
|
|
100
|
+
{ label: 'Item 1', color: '#ff0000', value: 0 },
|
|
101
|
+
{ label: 'Item 2', color: '#00ff00', value: '0%' },
|
|
102
|
+
];
|
|
103
|
+
render( <BaseLegend items={ itemsWithZeroValue } orientation="horizontal" /> );
|
|
104
|
+
expect( screen.getByText( '0' ) ).toBeInTheDocument();
|
|
105
|
+
expect( screen.getByText( '0%' ) ).toBeInTheDocument();
|
|
106
|
+
} );
|
|
107
|
+
|
|
108
|
+
test( 'renders each value next to its corresponding label by index', () => {
|
|
109
|
+
const itemsWithDistinctValues = [
|
|
110
|
+
{ label: 'Alpha', value: '100', color: '#ff0000' },
|
|
111
|
+
{ label: 'Beta', value: '200', color: '#00ff00' },
|
|
112
|
+
{ label: 'Gamma', value: '300', color: '#0000ff' },
|
|
113
|
+
];
|
|
114
|
+
render( <BaseLegend items={ itemsWithDistinctValues } orientation="horizontal" /> );
|
|
115
|
+
const legendItems = screen.getAllByTestId( 'legend-item' );
|
|
116
|
+
expect( legendItems ).toHaveLength( 3 );
|
|
117
|
+
expect( legendItems[ 0 ] ).toHaveTextContent( /Alpha.*100/ );
|
|
118
|
+
expect( legendItems[ 1 ] ).toHaveTextContent( /Beta.*200/ );
|
|
119
|
+
expect( legendItems[ 2 ] ).toHaveTextContent( /Gamma.*300/ );
|
|
120
|
+
} );
|
|
121
|
+
|
|
73
122
|
test( 'applies custom className', () => {
|
|
74
123
|
render(
|
|
75
124
|
<BaseLegend items={ defaultItems } className="custom-legend" orientation="horizontal" />
|
|
@@ -106,108 +155,155 @@ describe( 'BaseLegend', () => {
|
|
|
106
155
|
{ label: 'Another Long Label for Testing', value: '30%', color: '#00ff00' },
|
|
107
156
|
];
|
|
108
157
|
|
|
109
|
-
test( '
|
|
110
|
-
render(
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
158
|
+
test( 'applies maxWidth and minWidth styles to label text', () => {
|
|
159
|
+
render(
|
|
160
|
+
<BaseLegend
|
|
161
|
+
items={ longLabelItems }
|
|
162
|
+
labelStyles={ { maxWidth: '150px' } }
|
|
163
|
+
orientation="horizontal"
|
|
164
|
+
/>
|
|
165
|
+
);
|
|
166
|
+
const labels = screen.getAllByText( /Long Label/ );
|
|
167
|
+
labels.forEach( label => {
|
|
168
|
+
expect( label ).toHaveStyle( { maxWidth: '150px', minWidth: 0 } );
|
|
169
|
+
} );
|
|
115
170
|
} );
|
|
116
171
|
|
|
117
|
-
test( '
|
|
118
|
-
render(
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
172
|
+
test( 'supports different CSS units for maxWidth', () => {
|
|
173
|
+
render(
|
|
174
|
+
<BaseLegend
|
|
175
|
+
items={ longLabelItems }
|
|
176
|
+
labelStyles={ { maxWidth: '10rem' } }
|
|
177
|
+
orientation="horizontal"
|
|
178
|
+
/>
|
|
179
|
+
);
|
|
180
|
+
const labels = screen.getAllByText( /Long Label/ );
|
|
181
|
+
labels.forEach( label => {
|
|
182
|
+
expect( label ).toHaveStyle( { maxWidth: '10rem' } );
|
|
183
|
+
} );
|
|
122
184
|
} );
|
|
123
185
|
|
|
124
|
-
test( '
|
|
186
|
+
test( 'does not apply maxWidth styles when omitted', () => {
|
|
125
187
|
const { rerender } = render(
|
|
126
188
|
<BaseLegend items={ longLabelItems } orientation="horizontal" />
|
|
127
189
|
);
|
|
128
190
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
191
|
+
let labels = screen.getAllByText( /Long Label/ );
|
|
192
|
+
labels.forEach( label => {
|
|
193
|
+
expect( label ).not.toHaveStyle( { maxWidth: '150px' } );
|
|
194
|
+
} );
|
|
132
195
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
196
|
+
rerender(
|
|
197
|
+
<BaseLegend
|
|
198
|
+
items={ longLabelItems }
|
|
199
|
+
labelStyles={ { maxWidth: '150px' } }
|
|
200
|
+
orientation="horizontal"
|
|
201
|
+
/>
|
|
202
|
+
);
|
|
203
|
+
labels = screen.getAllByText( /Long Label/ );
|
|
204
|
+
labels.forEach( label => {
|
|
205
|
+
expect( label ).toHaveStyle( { maxWidth: '150px', minWidth: 0 } );
|
|
206
|
+
} );
|
|
137
207
|
} );
|
|
138
208
|
|
|
139
|
-
test( '
|
|
140
|
-
// Test ellipsis behavior - should render without errors
|
|
209
|
+
test( 'applies maxWidth styles for both textOverflow modes', () => {
|
|
141
210
|
const { rerender } = render(
|
|
142
211
|
<BaseLegend
|
|
143
212
|
items={ longLabelItems }
|
|
144
|
-
maxWidth
|
|
145
|
-
textOverflow="ellipsis"
|
|
213
|
+
labelStyles={ { maxWidth: '150px', textOverflow: 'ellipsis' } }
|
|
146
214
|
orientation="horizontal"
|
|
147
215
|
/>
|
|
148
216
|
);
|
|
149
217
|
let labels = screen.getAllByText( /Long Label/ );
|
|
150
|
-
|
|
218
|
+
labels.forEach( label => {
|
|
219
|
+
expect( label ).toHaveStyle( { maxWidth: '150px', minWidth: 0 } );
|
|
220
|
+
} );
|
|
151
221
|
|
|
152
|
-
// Test wrap behavior - should render without errors
|
|
153
222
|
rerender(
|
|
154
223
|
<BaseLegend
|
|
155
224
|
items={ longLabelItems }
|
|
156
|
-
maxWidth
|
|
157
|
-
textOverflow="wrap"
|
|
225
|
+
labelStyles={ { maxWidth: '150px', textOverflow: 'wrap' } }
|
|
158
226
|
orientation="horizontal"
|
|
159
227
|
/>
|
|
160
228
|
);
|
|
161
229
|
labels = screen.getAllByText( /Long Label/ );
|
|
162
|
-
|
|
230
|
+
labels.forEach( label => {
|
|
231
|
+
expect( label ).toHaveStyle( { maxWidth: '150px', minWidth: 0 } );
|
|
232
|
+
} );
|
|
163
233
|
} );
|
|
164
234
|
|
|
165
|
-
test( '
|
|
166
|
-
// Render with ellipsis overflow
|
|
235
|
+
test( 'applies maxWidth=0px correctly', () => {
|
|
167
236
|
render(
|
|
168
237
|
<BaseLegend
|
|
169
238
|
items={ longLabelItems }
|
|
170
|
-
maxWidth
|
|
171
|
-
textOverflow="ellipsis"
|
|
239
|
+
labelStyles={ { maxWidth: '0px', textOverflow: 'ellipsis' } }
|
|
172
240
|
orientation="horizontal"
|
|
173
241
|
/>
|
|
174
242
|
);
|
|
175
|
-
|
|
176
|
-
// Verify the text is rendered
|
|
177
243
|
const labels = screen.getAllByText( /Long Label/ );
|
|
178
|
-
|
|
244
|
+
labels.forEach( label => {
|
|
245
|
+
expect( label ).toHaveStyle( { maxWidth: '0px', minWidth: 0 } );
|
|
246
|
+
} );
|
|
179
247
|
} );
|
|
180
248
|
|
|
181
|
-
test( '
|
|
182
|
-
// Render with wrap overflow
|
|
249
|
+
test( 'applies legend-item-text--ellipsis class when textOverflow is ellipsis and maxWidth is set', () => {
|
|
183
250
|
render(
|
|
184
251
|
<BaseLegend
|
|
185
252
|
items={ longLabelItems }
|
|
186
|
-
maxWidth
|
|
187
|
-
textOverflow="wrap"
|
|
253
|
+
labelStyles={ { maxWidth: '150px', textOverflow: 'ellipsis' } }
|
|
188
254
|
orientation="horizontal"
|
|
189
255
|
/>
|
|
190
256
|
);
|
|
257
|
+
const labels = screen.getAllByText( /Long Label/ );
|
|
258
|
+
labels.forEach( label => {
|
|
259
|
+
expect( label ).toHaveClass( 'legend-item-text--ellipsis' );
|
|
260
|
+
expect( label ).not.toHaveClass( 'legend-item-text--wrap' );
|
|
261
|
+
} );
|
|
262
|
+
} );
|
|
191
263
|
|
|
192
|
-
|
|
264
|
+
test( 'applies legend-item-text--wrap class when textOverflow is wrap and maxWidth is set', () => {
|
|
265
|
+
render(
|
|
266
|
+
<BaseLegend
|
|
267
|
+
items={ longLabelItems }
|
|
268
|
+
labelStyles={ { maxWidth: '150px', textOverflow: 'wrap' } }
|
|
269
|
+
orientation="horizontal"
|
|
270
|
+
/>
|
|
271
|
+
);
|
|
193
272
|
const labels = screen.getAllByText( /Long Label/ );
|
|
194
|
-
|
|
273
|
+
labels.forEach( label => {
|
|
274
|
+
expect( label ).toHaveClass( 'legend-item-text--wrap' );
|
|
275
|
+
expect( label ).not.toHaveClass( 'legend-item-text--ellipsis' );
|
|
276
|
+
} );
|
|
195
277
|
} );
|
|
196
278
|
|
|
197
|
-
test( '
|
|
198
|
-
// maxWidth={0} should apply 0-pixel constraint (not be treated as "no constraint")
|
|
279
|
+
test( 'does not apply overflow class when maxWidth is not set', () => {
|
|
199
280
|
render(
|
|
200
281
|
<BaseLegend
|
|
201
282
|
items={ longLabelItems }
|
|
202
|
-
|
|
203
|
-
textOverflow="ellipsis"
|
|
283
|
+
labelStyles={ { textOverflow: 'ellipsis' } }
|
|
204
284
|
orientation="horizontal"
|
|
205
285
|
/>
|
|
206
286
|
);
|
|
287
|
+
const labels = screen.getAllByText( /Long Label/ );
|
|
288
|
+
labels.forEach( label => {
|
|
289
|
+
expect( label ).not.toHaveClass( 'legend-item-text--ellipsis' );
|
|
290
|
+
expect( label ).not.toHaveClass( 'legend-item-text--wrap' );
|
|
291
|
+
} );
|
|
292
|
+
} );
|
|
207
293
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
294
|
+
test( 'does not apply overflow class when maxWidth is not set and textOverflow is wrap', () => {
|
|
295
|
+
render(
|
|
296
|
+
<BaseLegend
|
|
297
|
+
items={ longLabelItems }
|
|
298
|
+
labelStyles={ { textOverflow: 'wrap' } }
|
|
299
|
+
orientation="horizontal"
|
|
300
|
+
/>
|
|
301
|
+
);
|
|
302
|
+
const labels = screen.getAllByText( /Long Label/ );
|
|
303
|
+
labels.forEach( label => {
|
|
304
|
+
expect( label ).not.toHaveClass( 'legend-item-text--wrap' );
|
|
305
|
+
expect( label ).not.toHaveClass( 'legend-item-text--ellipsis' );
|
|
306
|
+
} );
|
|
211
307
|
} );
|
|
212
308
|
} );
|
|
213
309
|
|
|
@@ -2,20 +2,22 @@ import { LegendOrdinal } from '@visx/legend';
|
|
|
2
2
|
import type { GlyphProps, LineStyles } from '@visx/xychart';
|
|
3
3
|
import type { ComponentProps, CSSProperties, ReactNode } from 'react';
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
type VisxLegendProps = Pick<
|
|
6
|
+
ComponentProps< typeof LegendOrdinal >,
|
|
7
|
+
'className' | 'shape' | 'fill' | 'size' | 'labelFormat' | 'labelTransform'
|
|
8
|
+
>;
|
|
7
9
|
|
|
8
|
-
export type
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
10
|
+
export type LegendItemStyles = {
|
|
11
|
+
/** Margin around each legend item. */
|
|
12
|
+
margin?: CSSProperties[ 'margin' ];
|
|
13
|
+
/** Flex direction for items within each legend entry. */
|
|
14
|
+
flexDirection?: 'row' | 'row-reverse' | 'column' | 'column-reverse';
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export type LegendLabelStyles = Pick< CSSProperties, 'justifyContent' | 'flex' | 'margin' > & {
|
|
16
18
|
/**
|
|
17
|
-
* Maximum width for legend
|
|
18
|
-
*
|
|
19
|
+
* Maximum width for legend label text as a CSS value (e.g. '200px', '50%', '10rem').
|
|
20
|
+
* When set, text overflow behavior is controlled by textOverflow.
|
|
19
21
|
*/
|
|
20
22
|
maxWidth?: string;
|
|
21
23
|
/**
|
|
@@ -24,11 +26,35 @@ export type BaseLegendProps = Omit< LegendOrdinalProps, 'shapeStyle' > & {
|
|
|
24
26
|
* - 'wrap': Wrap text to multiple lines (default, ideal for larger displays)
|
|
25
27
|
*/
|
|
26
28
|
textOverflow?: 'ellipsis' | 'wrap';
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export type LegendShapeStyles = {
|
|
32
|
+
/** Width of the legend shape in pixels. */
|
|
33
|
+
width?: number;
|
|
34
|
+
/** Height of the legend shape in pixels. */
|
|
35
|
+
height?: number;
|
|
36
|
+
/** Margin around the legend shape. */
|
|
37
|
+
margin?: CSSProperties[ 'margin' ];
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export type BaseLegendProps = VisxLegendProps & {
|
|
41
|
+
items: BaseLegendItem[];
|
|
42
|
+
orientation?: 'horizontal' | 'vertical';
|
|
27
43
|
/**
|
|
28
|
-
*
|
|
29
|
-
* This allows consumers to customize individual legend item styling.
|
|
44
|
+
* TODO: Add 'left' | 'right' positioning support in future implementation
|
|
30
45
|
*/
|
|
31
|
-
|
|
46
|
+
position?: 'top' | 'bottom';
|
|
47
|
+
alignment?: 'start' | 'center' | 'end';
|
|
48
|
+
/** Additional CSS class name for legend items. */
|
|
49
|
+
itemClassName?: string;
|
|
50
|
+
/** CSS styles for each legend item (margin, flexDirection). */
|
|
51
|
+
itemStyles?: LegendItemStyles;
|
|
52
|
+
/** Additional CSS class name for legend labels. */
|
|
53
|
+
labelClassName?: string;
|
|
54
|
+
/** CSS styles for legend labels (justifyContent, flex, margin). */
|
|
55
|
+
labelStyles?: LegendLabelStyles;
|
|
56
|
+
/** Styles for legend shapes (width, height, margin). */
|
|
57
|
+
shapeStyles?: LegendShapeStyles;
|
|
32
58
|
/**
|
|
33
59
|
* Function for rendering a custom legend layout.
|
|
34
60
|
*/
|
|
@@ -51,7 +77,7 @@ export type LegendProps = Omit< BaseLegendProps, 'items' > & {
|
|
|
51
77
|
|
|
52
78
|
export type BaseLegendItem = {
|
|
53
79
|
label: string;
|
|
54
|
-
value
|
|
80
|
+
value?: number | string;
|
|
55
81
|
color: string;
|
|
56
82
|
glyphSize?: number;
|
|
57
83
|
renderGlyph?: < Datum extends object >( props: GlyphProps< Datum > ) => ReactNode;
|