@automattic/charts 1.6.0 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +2 -2
  3. package/dist/index.cjs +380 -60
  4. package/dist/index.cjs.map +1 -1
  5. package/dist/index.css +12 -7
  6. package/dist/index.d.cts +119 -30
  7. package/dist/index.d.ts +122 -33
  8. package/dist/index.js +380 -60
  9. package/dist/index.js.map +1 -1
  10. package/package.json +22 -21
  11. package/src/charts/area-chart/types.ts +1 -1
  12. package/src/charts/bar-chart/bar-chart.tsx +197 -43
  13. package/src/charts/bar-chart/private/comparison-bars-geometry.ts +70 -0
  14. package/src/charts/bar-chart/private/comparison-bars.tsx +154 -0
  15. package/src/charts/bar-chart/private/comparison-constants.ts +33 -0
  16. package/src/charts/bar-chart/private/index.ts +9 -0
  17. package/src/charts/bar-chart/private/test/comparison-bars-geometry.test.ts +47 -0
  18. package/src/charts/bar-chart/private/test/comparison-bars.test.tsx +183 -0
  19. package/src/charts/bar-chart/private/use-bar-chart-options.ts +46 -5
  20. package/src/charts/bar-chart/test/bar-chart.test.tsx +191 -1
  21. package/src/charts/geo-chart/geo-chart.tsx +8 -10
  22. package/src/charts/leaderboard-chart/leaderboard-chart.module.scss +27 -14
  23. package/src/charts/leaderboard-chart/leaderboard-chart.tsx +7 -4
  24. package/src/charts/leaderboard-chart/test/leaderboard-chart.test.tsx +51 -4
  25. package/src/charts/line-chart/line-chart.tsx +1 -1
  26. package/src/charts/line-chart/private/line-chart-annotation-label-popover.tsx +18 -2
  27. package/src/charts/line-chart/private/line-chart-annotation.tsx +1 -1
  28. package/src/charts/line-chart/types.ts +1 -1
  29. package/src/charts/pie-chart/pie-chart.module.scss +0 -4
  30. package/src/charts/pie-chart/pie-chart.tsx +3 -8
  31. package/src/charts/pie-semi-circle-chart/pie-semi-circle-chart.module.scss +0 -5
  32. package/src/charts/pie-semi-circle-chart/pie-semi-circle-chart.tsx +5 -10
  33. package/src/charts/private/center/center.module.scss +4 -0
  34. package/src/charts/private/center/center.tsx +33 -0
  35. package/src/charts/private/center/index.ts +2 -0
  36. package/src/charts/private/center/test/center.test.tsx +60 -0
  37. package/src/charts/private/chart-layout/chart-layout.tsx +1 -2
  38. package/src/charts/private/svg-empty-state/svg-empty-state.module.scss +0 -2
  39. package/src/charts/private/svg-empty-state/svg-empty-state.tsx +2 -4
  40. package/src/charts/private/x-zoom.tsx +2 -2
  41. package/src/components/legend/hooks/use-chart-legend-items.ts +6 -2
  42. package/src/components/legend/legend.tsx +1 -2
  43. package/src/components/legend/utils/label-transform-factory.ts +1 -1
  44. package/src/components/tooltip/accessible-tooltip.tsx +2 -6
  45. package/src/index.ts +6 -5
  46. package/src/providers/chart-context/global-charts-provider.tsx +2 -0
  47. package/src/providers/chart-context/test/chart-context.test.tsx +13 -1
  48. package/src/providers/chart-context/themes.ts +9 -1
  49. package/src/providers/chart-context/types.ts +9 -2
  50. package/src/types.ts +120 -11
  51. package/src/utils/get-styles.ts +36 -14
  52. package/src/utils/index.ts +6 -1
  53. package/src/utils/test/get-styles.test.ts +47 -1
  54. package/src/visx/types.ts +30 -1
@@ -54,16 +54,18 @@ const defaultDeltaFormatter = ( value: number ): string => {
54
54
 
55
55
  /**
56
56
  * Build a bar's width. A hover-inset CSS variable (0 by default) is subtracted
57
- * so interactive rows can pull the bar's right edge back by a fixed pixel amount
58
- * on hover instead of a percentage scale keeping the bar↔value gap constant.
57
+ * on hover, scaled by the bar's share so the pull-back is proportional to its
58
+ * length: the full-length (100%) bar the one that reaches the value pulls
59
+ * back the whole inset to keep its gap with the value, while shorter bars pull
60
+ * back proportionally less, down to ~0 for a very short bar.
59
61
  *
60
62
  * @param share - The bar's share of the row width, as a percentage.
61
63
  * @return A CSS width value.
62
64
  */
63
65
  const getBarWidth = ( share: number ): string =>
64
- `calc(${ share }% - var(--a8c--charts--leaderboard--bar--hover-inset, 0px))`;
66
+ `calc(${ share }% - var(--a8c--charts--leaderboard--bar--hover-inset, 0px) * ${ share } / 100)`;
65
67
 
66
- const BarLabel = ( { label }: { label: string | JSX.Element } ) => (
68
+ const BarLabel = ( { label }: { label: LeaderboardEntry[ 'label' ] } ) => (
67
69
  <>{ typeof label === 'string' ? <Text className={ styles.label }>{ label }</Text> : label }</>
68
70
  );
69
71
 
@@ -376,6 +378,7 @@ const LeaderboardChartInternal: FC< LeaderboardChartProps > = ( {
376
378
  type="button"
377
379
  className={ styles.interactiveRow }
378
380
  onClick={ entry.onClick }
381
+ aria-label={ entry.ariaLabel }
379
382
  >
380
383
  { rowCells }
381
384
  <Icon className={ styles.chevron } icon={ chevronRight } size={ 24 } />
@@ -1,4 +1,5 @@
1
- import { render, screen, fireEvent } from '@testing-library/react';
1
+ import { render, screen } from '@testing-library/react';
2
+ import userEvent from '@testing-library/user-event';
2
3
  import LeaderboardChart from '../leaderboard-chart';
3
4
  import type { LeaderboardEntry } from '../../../types';
4
5
 
@@ -375,20 +376,66 @@ describe( 'LeaderboardChart', () => {
375
376
  expect( screen.getByRole( 'button' ).tagName ).toBe( 'BUTTON' );
376
377
  } );
377
378
 
378
- it( 'calls onClick when the row is clicked', () => {
379
+ it( 'calls onClick when the row is clicked', async () => {
380
+ const user = userEvent.setup();
379
381
  const onClick = jest.fn();
380
382
  render( <LeaderboardChart data={ [ { ...mockData[ 0 ], onClick } ] } /> );
381
- // eslint-disable-next-line testing-library/prefer-user-event
382
- fireEvent.click( screen.getByRole( 'button' ) );
383
+ await user.click( screen.getByRole( 'button' ) );
383
384
  expect( onClick ).toHaveBeenCalledTimes( 1 );
384
385
  } );
385
386
 
387
+ it( 'activates onClick via the keyboard (Enter and Space)', async () => {
388
+ const user = userEvent.setup();
389
+ const onClick = jest.fn();
390
+ render( <LeaderboardChart data={ [ { ...mockData[ 0 ], onClick } ] } /> );
391
+
392
+ await user.tab();
393
+ expect( screen.getByRole( 'button' ) ).toHaveFocus();
394
+
395
+ await user.keyboard( '[Enter]' );
396
+ await user.keyboard( '[Space]' );
397
+ expect( onClick ).toHaveBeenCalledTimes( 2 );
398
+ } );
399
+
386
400
  it( 'gives the interactive row an accessible name from the label and value', () => {
387
401
  render( <LeaderboardChart data={ [ { ...mockData[ 0 ], onClick: jest.fn() } ] } /> );
388
402
  // mockData[0] label is 'Direct', currentValue 12500 → '12.5K'
389
403
  expect( screen.getByRole( 'button' ) ).toHaveAccessibleName( /Direct.*12\.5K/ );
390
404
  } );
391
405
 
406
+ it( 'derives the accessible name from an image label via its alt text', () => {
407
+ render(
408
+ <LeaderboardChart
409
+ data={ [
410
+ {
411
+ ...mockData[ 0 ],
412
+ label: <img src="https://example.com/flag.svg" alt="United States" />,
413
+ onClick: jest.fn(),
414
+ },
415
+ ] }
416
+ />
417
+ );
418
+ expect( screen.getByRole( 'button' ) ).toHaveAccessibleName( /United States.*12\.5K/ );
419
+ } );
420
+
421
+ it( 'uses ariaLabel as the accessible name when provided', () => {
422
+ render(
423
+ <LeaderboardChart
424
+ data={ [
425
+ {
426
+ ...mockData[ 0 ],
427
+ label: <img src="https://example.com/flag.svg" alt="" />,
428
+ ariaLabel: 'United States: 12.5K visitors',
429
+ onClick: jest.fn(),
430
+ },
431
+ ] }
432
+ />
433
+ );
434
+ expect( screen.getByRole( 'button' ) ).toHaveAccessibleName(
435
+ 'United States: 12.5K visitors'
436
+ );
437
+ } );
438
+
392
439
  it( 'does not render a button for entries without onClick', () => {
393
440
  render( <LeaderboardChart data={ [ mockData[ 0 ] ] } /> );
394
441
  expect( screen.queryByRole( 'button' ) ).not.toBeInTheDocument();
@@ -42,10 +42,10 @@ import styles from './line-chart.module.scss';
42
42
  import { LineChartAnnotation, LineChartAnnotationsOverlay, LineChartGlyph } from './private';
43
43
  import type { RenderLineGlyphProps, LineChartProps, TooltipDatum } from './types';
44
44
  import type { DataPoint, DataPointDate, SeriesData, Optional } from '../../types';
45
+ import type { RenderTooltipParams } from '../../visx/types';
45
46
  import type { ResponsiveConfig } from '../private/with-responsive';
46
47
  import type { TickFormatter } from '@visx/axis';
47
48
  import type { GlyphProps } from '@visx/xychart';
48
- import type { RenderTooltipParams } from '@visx/xychart/lib/components/Tooltip';
49
49
  import type { FC, Ref } from 'react';
50
50
 
51
51
  const defaultRenderGlyph = < Datum extends object >( props: RenderLineGlyphProps< Datum > ) => {
@@ -1,5 +1,4 @@
1
1
  import { __ } from '@wordpress/i18n';
2
- import { Icon, close } from '@wordpress/icons';
3
2
  import { Stack } from '@wordpress/ui';
4
3
  import clsx from 'clsx';
5
4
  import { useEffect, useId, useRef, useState } from 'react';
@@ -10,6 +9,23 @@ import type { FC } from 'react';
10
9
 
11
10
  export const POPOVER_BUTTON_SIZE = 44;
12
11
 
12
+ const CloseIcon = () => (
13
+ <svg
14
+ width="16"
15
+ height="16"
16
+ viewBox="0 0 24 24"
17
+ fill="none"
18
+ stroke="currentColor"
19
+ strokeWidth="2"
20
+ strokeLinecap="round"
21
+ strokeLinejoin="round"
22
+ aria-hidden="true"
23
+ focusable="false"
24
+ >
25
+ <path d="M6 6l12 12M18 6L6 18" />
26
+ </svg>
27
+ );
28
+
13
29
  interface LineChartAnnotationLabelWithPopoverProps {
14
30
  title: string;
15
31
  subtitle?: string;
@@ -101,7 +117,7 @@ const LineChartAnnotationLabelWithPopover: FC< LineChartAnnotationLabelWithPopov
101
117
  className={ styles[ 'line-chart__annotation-label-popover-close-button' ] }
102
118
  aria-label={ __( 'Close', 'jetpack-charts' ) }
103
119
  >
104
- <Icon icon={ close } size={ 16 } />
120
+ <CloseIcon />
105
121
  </button>
106
122
  </Stack>
107
123
  </div>
@@ -15,7 +15,7 @@ import LineChartAnnotationLabelWithPopover, {
15
15
  POPOVER_BUTTON_SIZE,
16
16
  } from './line-chart-annotation-label-popover';
17
17
  import type { LineChartAnnotationProps } from '../types';
18
- import type { LabelProps } from '@visx/annotation/lib/components/Label';
18
+ import type { LabelProps } from '@visx/annotation';
19
19
  import type { TextProps } from '@visx/text';
20
20
  import type { FC } from 'react';
21
21
 
@@ -5,8 +5,8 @@ import type {
5
5
  AnnotationStyles,
6
6
  DataPoint,
7
7
  } from '../../types';
8
+ import type { RenderTooltipParams } from '../../visx/types';
8
9
  import type { GlyphProps } from '@visx/xychart';
9
- import type { RenderTooltipParams } from '@visx/xychart/lib/components/Tooltip';
10
10
  import type { ReactNode, SVGProps, FC } from 'react';
11
11
 
12
12
  export type LineChartAnnotationProps = {
@@ -7,8 +7,4 @@
7
7
  width: 100%;
8
8
  }
9
9
 
10
- &__centering {
11
- width: 100%;
12
- height: 100%;
13
- }
14
10
  }
@@ -2,7 +2,6 @@ import { Group } from '@visx/group';
2
2
  import { Pie } from '@visx/shape';
3
3
  import { useTooltip, useTooltipInPortal } from '@visx/tooltip';
4
4
  import { __ } from '@wordpress/i18n';
5
- import { Stack } from '@wordpress/ui';
6
5
  import clsx from 'clsx';
7
6
  import { useCallback, useContext, useMemo } from 'react';
8
7
  import { Legend, useChartLegendItems } from '../../components/legend';
@@ -22,6 +21,7 @@ import {
22
21
  } from '../../providers';
23
22
  import { attachSubComponents, resolveFontSize } from '../../utils';
24
23
  import { getStringWidth } from '../../visx/text';
24
+ import { Center } from '../private/center';
25
25
  import { ChartSVG, ChartHTML, useChartChildren } from '../private/chart-composition';
26
26
  import { ChartLayout } from '../private/chart-layout';
27
27
  import { RadialWipeAnimation } from '../private/radial-wipe-animation/';
@@ -358,12 +358,7 @@ const PieChartInternal = ( {
358
358
  : 0;
359
359
 
360
360
  return (
361
- <Stack
362
- ref={ containerRef }
363
- align="center"
364
- justify="center"
365
- className={ styles[ 'pie-chart__centering' ] }
366
- >
361
+ <Center ref={ containerRef }>
367
362
  <svg
368
363
  viewBox={ `0 0 ${ width } ${ height }` }
369
364
  preserveAspectRatio="xMidYMid meet"
@@ -493,7 +488,7 @@ const PieChartInternal = ( {
493
488
  { ! allSegmentsHidden && svgChildren }
494
489
  </Group>
495
490
  </svg>
496
- </Stack>
491
+ </Center>
497
492
  );
498
493
  } }
499
494
  </ChartLayout>
@@ -5,11 +5,6 @@
5
5
  width: 100%;
6
6
  }
7
7
 
8
- &__centering {
9
- width: 100%;
10
- height: 100%;
11
- }
12
-
13
8
  .label {
14
9
  font-weight: var(--wpds-typography-font-weight-medium, 499);
15
10
  font-size: var(--wpds-typography-font-size-lg, 16px);
@@ -3,7 +3,6 @@ import { Pie } from '@visx/shape';
3
3
  import { Text } from '@visx/text';
4
4
  import { useTooltip, useTooltipInPortal } from '@visx/tooltip';
5
5
  import { __ } from '@wordpress/i18n';
6
- import { Stack } from '@wordpress/ui';
7
6
  import clsx from 'clsx';
8
7
  import { useCallback, useContext, useMemo } from 'react';
9
8
  import { Legend, useChartLegendItems } from '../../components/legend';
@@ -21,6 +20,7 @@ import {
21
20
  GlobalChartsContext,
22
21
  } from '../../providers';
23
22
  import { attachSubComponents } from '../../utils';
23
+ import { Center } from '../private/center';
24
24
  import { ChartSVG, ChartHTML, useChartChildren } from '../private/chart-composition';
25
25
  import { ChartLayout } from '../private/chart-layout';
26
26
  import { RadialWipeAnimation } from '../private/radial-wipe-animation';
@@ -37,7 +37,7 @@ import type {
37
37
  } from '../../types';
38
38
  import type { ChartComponentWithComposition } from '../private/chart-composition';
39
39
  import type { ResponsiveConfig } from '../private/with-responsive';
40
- import type { PieArcDatum } from '@visx/shape/lib/shapes/Pie';
40
+ import type { PieProvidedProps } from '@visx/shape';
41
41
  import type { FC, MouseEvent, ReactNode } from 'react';
42
42
 
43
43
  /**
@@ -135,7 +135,7 @@ type PieSemiCircleChartResponsiveComponent = ChartComponentWithComposition<
135
135
  PieSemiCircleChartBaseProps & ResponsiveConfig
136
136
  >;
137
137
 
138
- export type ArcData = PieArcDatum< DataPointPercentageCalculated >;
138
+ export type ArcData = PieProvidedProps< DataPointPercentageCalculated >[ 'arcs' ][ number ];
139
139
 
140
140
  /**
141
141
  * Validates the semi-circle pie chart data
@@ -397,12 +397,7 @@ const PieSemiCircleChartInternal: FC< PieSemiCircleChartProps > = ( {
397
397
  const innerRadius = radius * ( 1 - thickness );
398
398
 
399
399
  return (
400
- <Stack
401
- ref={ containerRef }
402
- align="center"
403
- justify="center"
404
- className={ styles[ 'pie-semi-circle-chart__centering' ] }
405
- >
400
+ <Center ref={ containerRef }>
406
401
  <svg
407
402
  width={ width }
408
403
  height={ height }
@@ -491,7 +486,7 @@ const PieSemiCircleChartInternal: FC< PieSemiCircleChartProps > = ( {
491
486
  ) }
492
487
  </Group>
493
488
  </svg>
494
- </Stack>
489
+ </Center>
495
490
  );
496
491
  } }
497
492
  </ChartLayout>
@@ -0,0 +1,4 @@
1
+ .center {
2
+ width: 100%;
3
+ height: 100%;
4
+ }
@@ -0,0 +1,33 @@
1
+ import { Stack } from '@wordpress/ui';
2
+ import clsx from 'clsx';
3
+ import { forwardRef, type ComponentPropsWithoutRef } from 'react';
4
+ import styles from './center.module.scss';
5
+
6
+ export type CenterProps = ComponentPropsWithoutRef< typeof Stack >;
7
+
8
+ /**
9
+ * Centers its children on both axes and fills its parent.
10
+ *
11
+ * A thin wrapper around `Stack` with `align="center"` and `justify="center"`
12
+ * defaults (both overridable) plus `width: 100%; height: 100%`. Reads more
13
+ * honestly than a `Stack` with both axes centered, and lets call sites drop
14
+ * ad-hoc `*__centering` classes. Forwards its ref and spreads remaining props
15
+ * onto the underlying `Stack`.
16
+ *
17
+ * @param props - Stack props; `align`/`justify` default to `"center"`.
18
+ * @param ref - Forwarded to the underlying element.
19
+ * @return The centered layout element.
20
+ */
21
+ export const Center = forwardRef< HTMLDivElement, CenterProps >(
22
+ ( { align = 'center', justify = 'center', className, ...props }, ref ) => (
23
+ <Stack
24
+ ref={ ref }
25
+ align={ align }
26
+ justify={ justify }
27
+ className={ clsx( styles.center, className ) }
28
+ { ...props }
29
+ />
30
+ )
31
+ );
32
+
33
+ Center.displayName = 'Center';
@@ -0,0 +1,2 @@
1
+ export { Center } from './center';
2
+ export type { CenterProps } from './center';
@@ -0,0 +1,60 @@
1
+ import { render, screen } from '@testing-library/react';
2
+ import { createRef } from 'react';
3
+ import { Center } from '../index';
4
+
5
+ describe( 'Center', () => {
6
+ test( 'renders its children', () => {
7
+ render(
8
+ <Center data-testid="center">
9
+ <span>Centered content</span>
10
+ </Center>
11
+ );
12
+ expect( screen.getByTestId( 'center' ) ).toHaveTextContent( 'Centered content' );
13
+ } );
14
+
15
+ test( 'merges a custom className with the center class', () => {
16
+ render(
17
+ <Center data-testid="center" className="custom-class">
18
+ child
19
+ </Center>
20
+ );
21
+ const el = screen.getByTestId( 'center' );
22
+ expect( el ).toHaveClass( 'center' );
23
+ expect( el ).toHaveClass( 'custom-class' );
24
+ } );
25
+
26
+ test( 'forwards its ref to the underlying element', () => {
27
+ const ref = createRef< HTMLDivElement >();
28
+ render( <Center ref={ ref }>child</Center> );
29
+ expect( ref.current ).toBeInstanceOf( HTMLElement );
30
+ } );
31
+
32
+ test( 'spreads arbitrary props through to the element', () => {
33
+ render(
34
+ <Center data-testid="center" style={ { width: 120 } }>
35
+ child
36
+ </Center>
37
+ );
38
+ expect( screen.getByTestId( 'center' ) ).toHaveStyle( { width: '120px' } );
39
+ } );
40
+
41
+ test( 'centers on both axes by default', () => {
42
+ render( <Center data-testid="center">child</Center> );
43
+ expect( screen.getByTestId( 'center' ) ).toHaveStyle( {
44
+ alignItems: 'center',
45
+ justifyContent: 'center',
46
+ } );
47
+ } );
48
+
49
+ test( 'allows overriding the align and justify defaults', () => {
50
+ render(
51
+ <Center data-testid="center" align="flex-start" justify="flex-end">
52
+ child
53
+ </Center>
54
+ );
55
+ expect( screen.getByTestId( 'center' ) ).toHaveStyle( {
56
+ alignItems: 'flex-start',
57
+ justifyContent: 'flex-end',
58
+ } );
59
+ } );
60
+ } );
@@ -3,9 +3,8 @@ import { useEffect } from 'react';
3
3
  import { useElementSize } from '../../../hooks';
4
4
  import { renderLegendSlot } from '../chart-composition';
5
5
  import styles from './chart-layout.module.scss';
6
- import type { LegendPosition } from '../../../types';
6
+ import type { GapSize, LegendPosition } from '../../../types';
7
7
  import type { LegendChild } from '../chart-composition/use-chart-children';
8
- import type { GapSize } from '@wordpress/theme';
9
8
  import type { CSSProperties, ReactNode } from 'react';
10
9
 
11
10
  /**
@@ -1,7 +1,5 @@
1
1
  .svg-empty-state {
2
2
  text-align: center;
3
- width: 100%;
4
- height: 100%;
5
3
  font-size: var(--wpds-typography-font-size-md, 13px);
6
4
  color: var(--wpds-color-fg-content-neutral-weak, #6d6d6d);
7
5
  }
@@ -1,4 +1,4 @@
1
- import { Stack } from '@wordpress/ui';
1
+ import { Center } from '../center';
2
2
  import styles from './svg-empty-state.module.scss';
3
3
  import type { FC, ReactNode } from 'react';
4
4
 
@@ -32,9 +32,7 @@ interface SvgEmptyStateProps {
32
32
  export const SvgEmptyState: FC< SvgEmptyStateProps > = ( { x, y, width, height, children } ) => {
33
33
  return (
34
34
  <foreignObject x={ x - width / 2 } y={ y - height / 2 } width={ width } height={ height }>
35
- <Stack align="center" justify="center" className={ styles[ 'svg-empty-state' ] }>
36
- { children }
37
- </Stack>
35
+ <Center className={ styles[ 'svg-empty-state' ] }>{ children }</Center>
38
36
  </foreignObject>
39
37
  );
40
38
  };
@@ -5,7 +5,7 @@ import styles from './x-zoom.module.scss';
5
5
  import type { SingleChartRef } from './single-chart-context';
6
6
  import type { AxisScale } from '@visx/axis';
7
7
  import type { EventHandlerParams } from '@visx/xychart';
8
- import type { MutableRefObject, ReactNode } from 'react';
8
+ import type { ReactNode, RefObject } from 'react';
9
9
 
10
10
  const MIN_DRAG_PIXELS = 6;
11
11
 
@@ -37,7 +37,7 @@ export function useXZoom< T extends Date | number = Date >( {
37
37
  userHandlers,
38
38
  }: {
39
39
  enabled: boolean;
40
- chartRef: MutableRefObject< SingleChartRef | null >;
40
+ chartRef: RefObject< SingleChartRef | null >;
41
41
  userHandlers?: {
42
42
  onPointerDown?: PointerHandler;
43
43
  onPointerMove?: PointerHandler;
@@ -6,9 +6,13 @@ import {
6
6
  type ElementStyles,
7
7
  } from '../../../providers';
8
8
  import { formatPercentage } from '../../../utils';
9
- import type { SeriesData, DataPointDate, DataPointPercentageCalculated } from '../../../types';
9
+ import type {
10
+ SeriesData,
11
+ DataPointDate,
12
+ DataPointPercentageCalculated,
13
+ LegendShape,
14
+ } from '../../../types';
10
15
  import type { BaseLegendItem } from '../types';
11
- import type { LegendShape } from '@visx/legend/lib/types';
12
16
  import type { GlyphProps } from '@visx/xychart';
13
17
  import type { ReactNode } from 'react';
14
18
 
@@ -3,8 +3,7 @@ import { SingleChartContext } from '../../charts/private/single-chart-context';
3
3
  import { GlobalChartsContext } from '../../providers';
4
4
  import { BaseLegend } from './private';
5
5
  import type { LegendProps } from './types';
6
- import type { ChartType } from '../../types';
7
- import type { LegendShape } from '@visx/legend/lib/types';
6
+ import type { ChartType, LegendShape } from '../../types';
8
7
 
9
8
  const defaultShapeByChartType: Partial<
10
9
  Record< ChartType, Extract< LegendShape< unknown, unknown >, string > >
@@ -1,4 +1,4 @@
1
- import type { ItemTransformer, LabelFormatter } from '@visx/legend/lib/types';
1
+ import type { ItemTransformer, LabelFormatter } from '@visx/legend';
2
2
  import type { AnyD3Scale, ScaleInput } from '@visx/scale';
3
3
 
4
4
  /**
@@ -1,10 +1,7 @@
1
1
  import { Tooltip, TooltipContext } from '@visx/xychart';
2
2
  import { useContext, useEffect, useCallback, useMemo } from 'react';
3
3
  import type { SeriesData, DataPointDate } from '../../types';
4
- import type {
5
- TooltipProps as BaseTooltipProps,
6
- RenderTooltipParams,
7
- } from '@visx/xychart/lib/components/Tooltip';
4
+ import type { RenderTooltipParams, XyChartTooltipProps } from '../../visx/types';
8
5
  import type { ReactNode } from 'react';
9
6
 
10
7
  // Type for flattened tooltip data used in individual mode
@@ -17,7 +14,7 @@ export type FlattenedTooltipData = {
17
14
 
18
15
  // Enhanced tooltip with keyboard navigation and accessibility
19
16
  interface AccessibleTooltipProps
20
- extends Omit< BaseTooltipProps< DataPointDate >, 'renderTooltip' > {
17
+ extends Omit< XyChartTooltipProps< DataPointDate >, 'renderTooltip' > {
21
18
  renderTooltip?: ( params: RenderTooltipParams< DataPointDate > ) => ReactNode;
22
19
  selectedIndex?: number | undefined;
23
20
  tooltipRef?: ( element: HTMLDivElement | null ) => void;
@@ -242,4 +239,3 @@ export const useKeyboardNavigation = ( {
242
239
 
243
240
  // Re-export the base Tooltip for backwards compatibility
244
241
  export { Tooltip };
245
- export type { BaseTooltipProps };
package/src/index.ts CHANGED
@@ -54,6 +54,12 @@ export type {
54
54
  ChartLegendConfig,
55
55
  BaseChartProps,
56
56
  GridProps,
57
+ GoogleDataTableColumn,
58
+ GoogleDataTableColumnRoleType,
59
+ GoogleDataTableRow,
60
+ LegendShape,
61
+ LegendShapeLabel,
62
+ LegendShapeRenderProps,
57
63
  } from './types';
58
64
  export type * from './visx/types';
59
65
  export type { PieChartProps, PieChartRenderTooltipParams } from './charts/pie-chart';
@@ -65,11 +71,6 @@ export type { GeoChartProps, GeoRegion, GeoResolution } from './charts/geo-chart
65
71
  export type { LegendValueDisplay, BaseLegendItem } from './components/legend';
66
72
  export type { TrendIndicatorProps, TrendDirection } from './components/trend-indicator';
67
73
  export type { LineStyles, GridStyles, EventHandlerParams } from '@visx/xychart';
68
- export type {
69
- GoogleDataTableColumn,
70
- GoogleDataTableRow,
71
- GoogleDataTableColumnRoleType,
72
- } from 'react-google-charts';
73
74
 
74
75
  // Re-exports from removed individual entry points
75
76
  export { useLeaderboardLegendItems } from './charts/leaderboard-chart/hooks';
@@ -10,6 +10,7 @@ import {
10
10
  } from 'react';
11
11
  import {
12
12
  getItemShapeStyles,
13
+ getSeriesBarStyles,
13
14
  getSeriesLineStyles,
14
15
  mergeThemes,
15
16
  resolveCssVariable,
@@ -206,6 +207,7 @@ export const GlobalChartsProvider: FC< GlobalChartsProviderProps > = ( { childre
206
207
  ( isPointPercentageData && data?.color ),
207
208
  } ),
208
209
  lineStyles: isSeriesData ? getSeriesLineStyles( data, index, providerTheme ) : {},
210
+ barStyles: isSeriesData ? getSeriesBarStyles( data, index, providerTheme ) : {},
209
211
  glyph: providerTheme.glyphs?.[ index ],
210
212
  shapeStyles: isSeriesData
211
213
  ? getItemShapeStyles( data, index, providerTheme, legendShape )
@@ -4,6 +4,7 @@ import { GlobalChartsProvider } from '../global-charts-provider';
4
4
  import { useChartId } from '../hooks/use-chart-id';
5
5
  import { useChartRegistration } from '../hooks/use-chart-registration';
6
6
  import { useGlobalChartsContext } from '../hooks/use-global-charts-context';
7
+ import { defaultTheme } from '../themes';
7
8
  import type { BaseLegendItem } from '../../../components/legend';
8
9
  import type { ChartTheme, SeriesData } from '../../../types';
9
10
  import type { GlobalChartsContextValue } from '../types';
@@ -1165,10 +1166,12 @@ describe( 'ChartContext', () => {
1165
1166
  legendShape: 'rect',
1166
1167
  } );
1167
1168
 
1168
- // Should get theme legend shape styles, not line styles
1169
+ // Should get theme legend shape styles (not line styles), with the comparison bar
1170
+ // opacity layered on so the swatch matches the translucent comparison bar.
1169
1171
  expect( styles.shapeStyles ).toEqual( {
1170
1172
  fill: '#LEGEND1',
1171
1173
  stroke: '#BORDER1',
1174
+ opacity: 0.5,
1172
1175
  } );
1173
1176
  } );
1174
1177
  } );
@@ -2534,4 +2537,13 @@ describe( 'ChartContext', () => {
2534
2537
  } );
2535
2538
  } );
2536
2539
  } );
2540
+
2541
+ describe( 'defaultTheme', () => {
2542
+ it( 'exposes default barChart comparison styles', () => {
2543
+ expect( defaultTheme.barChart.barStyles.comparison ).toEqual( {
2544
+ widthFactor: 1.5,
2545
+ opacity: 0.5,
2546
+ } );
2547
+ } );
2548
+ } );
2537
2549
  } );
@@ -53,7 +53,7 @@ const defaultTheme: CompleteChartTheme = {
53
53
  leaderboardChart: {
54
54
  rowGap: 12,
55
55
  columnGap: 4,
56
- labelSpacing: 1.5,
56
+ labelSpacing: 'xs',
57
57
  deltaColors: [ '#FF8C8F', '#757575', '#1F9828' ], // [negative, neutral, positive]
58
58
  },
59
59
  conversionFunnelChart: {
@@ -69,6 +69,14 @@ const defaultTheme: CompleteChartTheme = {
69
69
  },
70
70
  },
71
71
  },
72
+ barChart: {
73
+ barStyles: {
74
+ comparison: {
75
+ widthFactor: 1.5,
76
+ opacity: 0.5,
77
+ },
78
+ },
79
+ },
72
80
  sparkline: {
73
81
  margin: { top: 2, right: 2, bottom: 2, left: 2 },
74
82
  strokeWidth: 1.5,
@@ -1,7 +1,13 @@
1
1
  import { CSSProperties, ReactNode } from 'react';
2
2
  import type { BaseLegendItem } from '../../components/legend';
3
- import type { ChartType, CompleteChartTheme, DataPointPercentage, SeriesData } from '../../types';
4
- import type { LegendShape } from '@visx/legend/lib/types';
3
+ import type {
4
+ BarStyles,
5
+ ChartType,
6
+ CompleteChartTheme,
7
+ DataPointPercentage,
8
+ LegendShape,
9
+ SeriesData,
10
+ } from '../../types';
5
11
  import type { GlyphProps, LineStyles } from '@visx/xychart';
6
12
 
7
13
  export interface ChartRegistration {
@@ -20,6 +26,7 @@ export type GetElementStylesParams = {
20
26
  export type ElementStyles = {
21
27
  color: string;
22
28
  lineStyles: LineStyles;
29
+ barStyles: BarStyles;
23
30
  glyph: < Datum extends object >( props: GlyphProps< Datum > ) => ReactNode;
24
31
  shapeStyles: CSSProperties & LineStyles;
25
32
  };