@automattic/charts 1.0.1 → 1.0.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@automattic/charts",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "Display charts within Automattic products.",
5
5
  "homepage": "https://github.com/Automattic/jetpack/tree/HEAD/projects/js-packages/charts/#readme",
6
6
  "bugs": {
@@ -63,7 +63,7 @@
63
63
  "typecheck": "tsgo --noEmit"
64
64
  },
65
65
  "dependencies": {
66
- "@automattic/number-formatters": "^1.1.3",
66
+ "@automattic/number-formatters": "^1.1.4",
67
67
  "@babel/runtime": "7.28.6",
68
68
  "@react-spring/web": "9.7.5",
69
69
  "@visx/annotation": "^3.12.0",
@@ -99,8 +99,8 @@
99
99
  "@babel/core": "7.29.0",
100
100
  "@babel/preset-react": "7.28.5",
101
101
  "@babel/preset-typescript": "7.28.5",
102
- "@storybook/addon-docs": "10.3.1",
103
- "@storybook/react": "10.3.1",
102
+ "@storybook/addon-docs": "10.3.3",
103
+ "@storybook/react": "10.3.3",
104
104
  "@testing-library/dom": "^10.0.0",
105
105
  "@testing-library/jest-dom": "^6.0.0",
106
106
  "@testing-library/react": "^16.0.0",
@@ -114,7 +114,7 @@
114
114
  "@wordpress/element": "6.42.0",
115
115
  "babel-jest": "30.3.0",
116
116
  "babel-plugin-react-remove-properties": "^0.3.1",
117
- "esbuild": "0.25.9",
117
+ "esbuild": "0.27.4",
118
118
  "esbuild-plugin-babel": "^0.2.3",
119
119
  "esbuild-sass-plugin": "^3.1.0",
120
120
  "identity-obj-proxy": "^3.0.0",
@@ -125,7 +125,7 @@
125
125
  "react": "18.3.1",
126
126
  "react-dom": "18.3.1",
127
127
  "sass-embedded": "1.97.3",
128
- "storybook": "10.3.1",
128
+ "storybook": "10.3.3",
129
129
  "tsup": "8.5.1",
130
130
  "typescript": "5.9.3"
131
131
  },
@@ -1,7 +1,7 @@
1
- .conversionFunnelChart {
1
+ .conversion-funnel-chart {
2
2
  font-family: var(--funnel-font-family, "SF Pro Text");
3
3
 
4
- &.loading {
4
+ &--loading {
5
5
  opacity: 0.6;
6
6
  pointer-events: none;
7
7
  }
@@ -53,9 +53,12 @@
53
53
  display: flex;
54
54
  flex-direction: column;
55
55
  height: 100%;
56
- transition: all 0.3s ease;
57
56
 
58
- &.blurred {
57
+ &--animated {
58
+ transition: opacity 0.3s ease;
59
+ }
60
+
61
+ &--blurred {
59
62
  opacity: 0.3;
60
63
  }
61
64
  }
@@ -95,24 +98,12 @@
95
98
  border-radius: 4px;
96
99
  position: relative;
97
100
  cursor: pointer;
98
- transition: all 0.2s ease;
99
-
100
-
101
- &.disabled {
102
- cursor: pointer;
103
- }
104
101
  }
105
102
 
106
103
  .funnel-bar {
107
104
  width: 100%;
108
105
  min-height: 4px;
109
106
  border-radius: 4px 4px 0 0;
110
- transition: all 0.3s ease;
111
-
112
- &.selected {
113
- box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2);
114
- filter: brightness(1.1);
115
- }
116
107
 
117
108
  &--animated {
118
109
  transform-origin: bottom;
@@ -52,7 +52,7 @@ const ConversionFunnelChartInternal: FC< ConversionFunnelChartProps > = ( {
52
52
  } ) => {
53
53
  const chartId = useChartId( providedChartId );
54
54
  const { conversionFunnelChart: conversionFunnelChartSettings } = useGlobalChartsTheme();
55
- const { getElementStyles } = useGlobalChartsContext();
55
+ const { getElementStyles, isColorPaletteResolved } = useGlobalChartsContext();
56
56
  const chartRef = useRef< HTMLDivElement >( null );
57
57
  const selectedBarRef = useRef< HTMLDivElement | null >( null );
58
58
 
@@ -301,7 +301,11 @@ const ConversionFunnelChartInternal: FC< ConversionFunnelChartProps > = ( {
301
301
  <Stack
302
302
  direction="column"
303
303
  data-testid="conversion-funnel-chart"
304
- className={ clsx( styles.conversionFunnelChart, loading && styles.loading, className ) }
304
+ className={ clsx(
305
+ styles[ 'conversion-funnel-chart' ],
306
+ loading && styles[ 'conversion-funnel-chart--loading' ],
307
+ className
308
+ ) }
305
309
  style={ { ...style, height: resolvedHeight } }
306
310
  >
307
311
  <div className={ styles[ 'empty-state' ] }>
@@ -324,7 +328,11 @@ const ConversionFunnelChartInternal: FC< ConversionFunnelChartProps > = ( {
324
328
  portalContainerRef( node );
325
329
  chartRef.current = node;
326
330
  } }
327
- className={ clsx( styles.conversionFunnelChart, loading && styles.loading, className ) }
331
+ className={ clsx(
332
+ styles[ 'conversion-funnel-chart' ],
333
+ loading && styles[ 'conversion-funnel-chart--loading' ],
334
+ className
335
+ ) }
328
336
  style={ { ...style, height: resolvedHeight } }
329
337
  >
330
338
  { /* Main Metric */ }
@@ -348,7 +356,12 @@ const ConversionFunnelChartInternal: FC< ConversionFunnelChartProps > = ( {
348
356
  return (
349
357
  <div
350
358
  key={ step.id }
351
- className={ clsx( styles[ 'funnel-step' ], isBlurred && styles.blurred ) }
359
+ data-testid="funnel-step"
360
+ className={ clsx(
361
+ styles[ 'funnel-step' ],
362
+ isColorPaletteResolved && styles[ 'funnel-step--animated' ],
363
+ isBlurred && styles[ 'funnel-step--blurred' ]
364
+ ) }
352
365
  >
353
366
  { /* Step Label and Rate */ }
354
367
  <div className={ styles[ 'step-header' ] }>
@@ -376,7 +389,7 @@ const ConversionFunnelChartInternal: FC< ConversionFunnelChartProps > = ( {
376
389
 
377
390
  { /* Funnel Bar */ }
378
391
  <div
379
- className={ clsx( styles[ 'bar-container' ], isBlurred && styles.disabled ) }
392
+ className={ styles[ 'bar-container' ] }
380
393
  onClick={ stepHandlers.get( step.id )?.onClick }
381
394
  onKeyDown={ stepHandlers.get( step.id )?.onKeyDown }
382
395
  role="button"
@@ -461,4 +461,15 @@ describe( 'ConversionFunnelChart', () => {
461
461
  expect( customRenderMainMetric ).toHaveBeenCalled();
462
462
  } );
463
463
  } );
464
+
465
+ describe( 'Color palette readiness', () => {
466
+ it( 'enables transitions once color palette is resolved', () => {
467
+ render( <ConversionFunnelChart { ...defaultProps } /> );
468
+
469
+ // After render, effects have run (via useEffect in GlobalChartsProvider),
470
+ // so the palette is resolved and the animated class is applied to funnel steps
471
+ const funnelStep = screen.getAllByTestId( 'funnel-step' )[ 0 ];
472
+ expect( funnelStep ).toHaveClass( 'funnel-step--animated' );
473
+ } );
474
+ } );
464
475
  } );
@@ -70,11 +70,16 @@ export const GlobalChartsProvider: FC< GlobalChartsProviderProps > = ( {
70
70
  maxHue: 0,
71
71
  } ) );
72
72
 
73
+ // Track if the color palette has been resolved from the DOM
74
+ // Useful for animations that should only run after the color palette is resolved
75
+ const [ isColorPaletteResolved, setIsColorPaletteResolved ] = useState( false );
76
+
73
77
  // Compute color cache after DOM is updated (so CSS variables are available)
74
78
  // Resolves CSS variables from the wrapper element's scope to handle scoped variables
75
79
  // Note: Only re-runs when providerTheme changes, not when wrapper element changes.
76
80
  // This is intentional, as wrapperRef is expected to be stable for the lifetime of the provider.
77
81
  useLayoutEffect( () => {
82
+ setIsColorPaletteResolved( false );
78
83
  const { colors } = providerTheme;
79
84
  const resolvedColors: string[] = [];
80
85
  const hues: number[] = [];
@@ -125,6 +130,12 @@ export const GlobalChartsProvider: FC< GlobalChartsProviderProps > = ( {
125
130
  } );
126
131
  }, [ providerTheme ] );
127
132
 
133
+ useEffect( () => {
134
+ if ( colorCache.colors.length > 0 ) {
135
+ setIsColorPaletteResolved( true );
136
+ }
137
+ }, [ colorCache ] );
138
+
128
139
  const [ groupToColorMap, setGroupToColorMap ] = useState< Map< string, string > >(
129
140
  () => new Map()
130
141
  );
@@ -271,6 +282,7 @@ export const GlobalChartsProvider: FC< GlobalChartsProviderProps > = ( {
271
282
  toggleSeriesVisibility,
272
283
  isSeriesVisible,
273
284
  getHiddenSeries,
285
+ isColorPaletteResolved,
274
286
  } ),
275
287
  [
276
288
  charts,
@@ -282,6 +294,7 @@ export const GlobalChartsProvider: FC< GlobalChartsProviderProps > = ( {
282
294
  toggleSeriesVisibility,
283
295
  isSeriesVisible,
284
296
  getHiddenSeries,
297
+ isColorPaletteResolved,
285
298
  ]
286
299
  );
287
300
 
@@ -47,6 +47,57 @@ describe( 'ChartContext', () => {
47
47
  expect( contextValue.charts ).toBeInstanceOf( Map );
48
48
  } );
49
49
 
50
+ it( 'exposes isColorPaletteResolved as true after render', () => {
51
+ let contextValue: GlobalChartsContextValue;
52
+
53
+ const TestComponent = () => {
54
+ contextValue = useGlobalChartsContext();
55
+ return <div>Test</div>;
56
+ };
57
+
58
+ render(
59
+ <GlobalChartsProvider>
60
+ <TestComponent />
61
+ </GlobalChartsProvider>
62
+ );
63
+
64
+ // After render and effects, isColorPaletteResolved should be true
65
+ expect( contextValue.isColorPaletteResolved ).toBe( true );
66
+ } );
67
+
68
+ it( 'resolves palette again after theme change', () => {
69
+ let contextValue: GlobalChartsContextValue;
70
+
71
+ const TestComponent = () => {
72
+ contextValue = useGlobalChartsContext();
73
+ return <div>Test</div>;
74
+ };
75
+
76
+ const theme1: Partial< ChartTheme > = {
77
+ colors: [ '#006DAB', '#1F9828' ],
78
+ };
79
+ const theme2: Partial< ChartTheme > = {
80
+ colors: [ '#FF0000', '#00FF00' ],
81
+ };
82
+
83
+ const { rerender } = render(
84
+ <GlobalChartsProvider theme={ theme1 }>
85
+ <TestComponent />
86
+ </GlobalChartsProvider>
87
+ );
88
+
89
+ expect( contextValue.isColorPaletteResolved ).toBe( true );
90
+
91
+ rerender(
92
+ <GlobalChartsProvider theme={ theme2 }>
93
+ <TestComponent />
94
+ </GlobalChartsProvider>
95
+ );
96
+
97
+ // After theme change, palette should re-resolve to true
98
+ expect( contextValue.isColorPaletteResolved ).toBe( true );
99
+ } );
100
+
50
101
  it( 'throws error when useGlobalChartsContext is used outside provider', () => {
51
102
  const TestComponent = () => {
52
103
  useGlobalChartsContext();
@@ -35,4 +35,5 @@ export interface GlobalChartsContextValue {
35
35
  toggleSeriesVisibility: ( chartId: string, seriesLabel: string ) => void;
36
36
  isSeriesVisible: ( chartId: string, seriesLabel: string ) => boolean;
37
37
  getHiddenSeries: ( chartId: string ) => Set< string >;
38
+ isColorPaletteResolved: boolean;
38
39
  }