@hero-design/rn 8.100.2 → 8.101.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 (36) hide show
  1. package/.turbo/turbo-build.log +3 -3
  2. package/CHANGELOG.md +6 -0
  3. package/es/index.js +109 -25
  4. package/lib/index.js +109 -25
  5. package/package.json +1 -1
  6. package/src/components/Chart/ColumnChart/ColumnChartContent.tsx +19 -3
  7. package/src/components/Chart/ColumnChart/Segment.tsx +1 -1
  8. package/src/components/Chart/ColumnChart/StackedSegment.tsx +10 -6
  9. package/src/components/Chart/ColumnChart/__tests__/Segment.spec.tsx +1 -1
  10. package/src/components/Chart/ColumnChart/__tests__/__snapshots__/StackedSegment.spec.tsx.snap +6 -21
  11. package/src/components/Chart/ColumnChart/__tests__/__snapshots__/index.spec.tsx.snap +999 -6
  12. package/src/components/Chart/ColumnChart/__tests__/index.spec.tsx +107 -0
  13. package/src/components/Chart/ColumnChart/index.tsx +15 -0
  14. package/src/components/Chart/Line/Line.tsx +5 -2
  15. package/src/components/Chart/Line/__tests__/Line.spec.tsx +13 -6
  16. package/src/components/Chart/Line/__tests__/__snapshots__/Line.spec.tsx.snap +1 -1
  17. package/src/components/Chart/Line/__tests__/__snapshots__/index.spec.tsx.snap +1464 -4
  18. package/src/components/Chart/Line/__tests__/index.spec.tsx +95 -1
  19. package/src/components/Chart/Line/index.tsx +14 -2
  20. package/src/components/Chart/shared/__tests__/utils.spec.ts +16 -0
  21. package/src/components/Chart/shared/constants.ts +4 -0
  22. package/src/components/Chart/shared/hooks/useCustomColor.ts +84 -0
  23. package/src/components/Chart/shared/utils.ts +14 -0
  24. package/src/components/Chart/types.ts +32 -0
  25. package/stats/8.100.2/rn-stats.html +1 -3
  26. package/stats/8.101.0/rn-stats.html +4844 -0
  27. package/types/components/Chart/ColumnChart/ColumnChartContent.d.ts +5 -1
  28. package/types/components/Chart/ColumnChart/StackedSegment.d.ts +4 -0
  29. package/types/components/Chart/ColumnChart/index.d.ts +8 -2
  30. package/types/components/Chart/Line/Line.d.ts +3 -1
  31. package/types/components/Chart/Line/index.d.ts +8 -2
  32. package/types/components/Chart/index.d.ts +2 -2
  33. package/types/components/Chart/shared/constants.d.ts +2 -0
  34. package/types/components/Chart/shared/hooks/useCustomColor.d.ts +22 -0
  35. package/types/components/Chart/shared/utils.d.ts +11 -0
  36. package/types/components/Chart/types.d.ts +14 -1
@@ -1,8 +1,13 @@
1
1
  import React from 'react';
2
2
  import { fireEvent } from '@testing-library/react-native';
3
+ import { mobileVisualisationPalette } from '@hero-design/colors';
3
4
  import Button from '../../../Button/Button';
4
5
  import renderWithTheme from '../../../../testHelpers/renderWithTheme';
5
- import LineChart from '..';
6
+ import LineChart, { LineChartProps } from '..';
7
+ import {
8
+ ERROR_COLOR_NOT_FOUND,
9
+ ERROR_COLOR_NUMBER_MISMATCH,
10
+ } from '../../shared/constants';
6
11
 
7
12
  const onActionPress = jest.fn();
8
13
 
@@ -109,4 +114,93 @@ describe('LineChart', () => {
109
114
  expect(getByText('Jun 2025')).toBeVisible();
110
115
  expect(getByText('Jul 2025')).toBeVisible();
111
116
  });
117
+
118
+ it('renders chart with custom colors', () => {
119
+ const customColorsProps: LineChartProps = {
120
+ ...lineChartProps,
121
+ data: [
122
+ { label: 'Series 1', data: [1, 2, 3] },
123
+ { label: 'Series 2', data: [4, 5, 6] },
124
+ ],
125
+ styleConfig: {
126
+ series: [
127
+ { label: 'Series 1', color: 'greenLight' },
128
+ { label: 'Series 2', color: 'blueLight' },
129
+ ],
130
+ },
131
+ };
132
+ const { getByTestId, getByText, toJSON } = renderWithTheme(
133
+ <LineChart {...customColorsProps} />
134
+ );
135
+
136
+ expect(getByTestId('line-Series 1').props.accessibilityLabel).toContain(
137
+ `color:${mobileVisualisationPalette.greenLight}`
138
+ );
139
+ expect(getByTestId('line-Series 2').props.accessibilityLabel).toContain(
140
+ `color:${mobileVisualisationPalette.blueLight}`
141
+ );
142
+
143
+ expect(toJSON()).toMatchSnapshot();
144
+ expect(getByText('May 2025')).toBeVisible();
145
+ expect(getByText('Jun 2025')).toBeVisible();
146
+ expect(getByText('Jul 2025')).toBeVisible();
147
+ });
148
+
149
+ it('should throw an error if the styleConfig does not have enough colors', () => {
150
+ expect(() =>
151
+ renderWithTheme(
152
+ <LineChart
153
+ {...lineChartProps}
154
+ styleConfig={{ series: [{ label: 'Series 1' }] }}
155
+ />
156
+ )
157
+ ).toThrow(ERROR_COLOR_NUMBER_MISMATCH);
158
+
159
+ expect(() =>
160
+ renderWithTheme(
161
+ <LineChart
162
+ {...lineChartProps}
163
+ styleConfig={{
164
+ series: [
165
+ { label: 'Series 1', color: 'greenLight' },
166
+ { label: 'Series 2', color: 'blueLight' },
167
+ { label: 'Series 3', color: 'redLight' },
168
+ ],
169
+ }}
170
+ />
171
+ )
172
+ ).toThrow(ERROR_COLOR_NUMBER_MISMATCH);
173
+
174
+ expect(() =>
175
+ renderWithTheme(
176
+ <LineChart
177
+ {...lineChartProps}
178
+ styleConfig={{
179
+ series: [
180
+ { label: 'Series 1', color: undefined },
181
+ { label: 'Series 2', color: 'blueLight' },
182
+ { label: 'Series 3', color: 'redLight' },
183
+ ],
184
+ }}
185
+ />
186
+ )
187
+ ).toThrow(ERROR_COLOR_NUMBER_MISMATCH);
188
+ });
189
+
190
+ it('should throw error if the color is not found in the palette', () => {
191
+ expect(() =>
192
+ renderWithTheme(
193
+ <LineChart
194
+ {...lineChartProps}
195
+ styleConfig={{
196
+ series: [
197
+ // @ts-expect-error: This is a test for error handling
198
+ { label: 'Series 1', color: 'mauve' },
199
+ { label: 'Series 2', color: 'blueLight' },
200
+ ],
201
+ }}
202
+ />
203
+ )
204
+ ).toThrow(ERROR_COLOR_NOT_FOUND.replace('{color}', 'mauve'));
205
+ });
112
206
  });
@@ -1,7 +1,7 @@
1
1
  import React, { useCallback, useMemo, useState } from 'react';
2
2
  import { LayoutChangeEvent, ViewStyle } from 'react-native';
3
3
  import ChartFrame from '../shared/ChartFrame';
4
- import useColorScale from '../shared/hooks/useColorScale';
4
+ import useCustomColor from '../shared/hooks/useCustomColor';
5
5
  import {
6
6
  createNiceScale,
7
7
  maxValueFromDataSet,
@@ -13,6 +13,7 @@ import {
13
13
  DataValue,
14
14
  HeaderConfig,
15
15
  Series,
16
+ StyleConfig,
16
17
  XAxisConfig,
17
18
  YAxisConfig,
18
19
  } from '../types';
@@ -47,6 +48,12 @@ export interface LineChartProps {
47
48
  * Text to display when the chart is empty.
48
49
  */
49
50
  emptyText?: string;
51
+ /**
52
+ * * styleConfig use to custom the style of the chart.
53
+ * * styleConfig must be an object:
54
+ * * color?: use to custom the legend colors.
55
+ */
56
+ styleConfig?: StyleConfig;
50
57
  }
51
58
 
52
59
  const LineChart = ({
@@ -57,10 +64,14 @@ const LineChart = ({
57
64
  testID,
58
65
  headerConfig,
59
66
  emptyText,
67
+ styleConfig,
60
68
  }: LineChartProps) => {
61
69
  const [chartSize, setChartSize] = useState({ width: 0, height: 0 });
62
70
 
63
- const colorScale = useColorScale(data.map((series) => series.label));
71
+ const colorScale = useCustomColor({
72
+ data,
73
+ seriesConfig: styleConfig?.series,
74
+ });
64
75
 
65
76
  const niceValues = useMemo(() => {
66
77
  const maxDataValue = maxValueFromDataSet(data);
@@ -106,6 +117,7 @@ const LineChart = ({
106
117
  data.map((series) => (
107
118
  <Line
108
119
  color={colorScale(series.label)}
120
+ testID={`line-${series.label}`}
109
121
  key={series.label}
110
122
  data={series.data}
111
123
  coordinates={coordinates}
@@ -0,0 +1,16 @@
1
+ import { filterElementHasNoColor } from '../utils';
2
+
3
+ describe('filterElementHasNoColor', () => {
4
+ it('should return an array of objects with color property', () => {
5
+ const data = [
6
+ { label: '07th', color: 'red' },
7
+ { label: '08th', color: 'blue' },
8
+ { label: '9th', color: undefined },
9
+ ];
10
+ const result = filterElementHasNoColor(data);
11
+ expect(result).toEqual([
12
+ { label: '07th', color: 'red' },
13
+ { label: '08th', color: 'blue' },
14
+ ]);
15
+ });
16
+ });
@@ -1,2 +1,6 @@
1
1
  export const DASH_ARRAY = [4, 4];
2
2
  export const DEFAULT_LINE_STROKE_WIDTH = 2;
3
+ export const ERROR_COLOR_NUMBER_MISMATCH =
4
+ 'styleConfig.series should have exact number of colors as the number of labels';
5
+ export const ERROR_COLOR_NOT_FOUND =
6
+ 'Color {color} not found in the mobile visualization palette.';
@@ -0,0 +1,84 @@
1
+ import { mobileVisualisationPalette } from '@hero-design/colors';
2
+ import { useMemo } from 'react';
3
+ import type { DataValue, Series, StyleConfig } from '../../types';
4
+ import {
5
+ ERROR_COLOR_NOT_FOUND,
6
+ ERROR_COLOR_NUMBER_MISMATCH,
7
+ } from '../constants';
8
+ import { filterElementHasNoColor } from '../utils';
9
+ import useColorScale from './useColorScale';
10
+
11
+ /**
12
+ * Props for the useCustomColor hook
13
+ */
14
+ type useCustomColorProps = {
15
+ /** Array of data series containing values and labels */
16
+ data: Array<Series<Array<DataValue>>>;
17
+ /** Optional configuration for custom series colors */
18
+ seriesConfig?: StyleConfig['series'];
19
+ };
20
+
21
+ /**
22
+ * Hook to handle custom color assignments for chart series
23
+ *
24
+ * This hook manages:
25
+ * 1. Validation of custom color configurations
26
+ * 2. Color mapping between series and palette
27
+ * 3. Fallback to default colors when custom colors aren't provided
28
+ *
29
+ * @throws {Error} When custom colors are invalid or don't match data series
30
+ */
31
+ const useCustomColor = ({ data, seriesConfig }: useCustomColorProps) => {
32
+ // Check if custom colors are provided and non-empty
33
+ const hasCustomColors = Boolean(seriesConfig?.length);
34
+
35
+ // Process and validate custom color segments
36
+ const customSegments = useMemo(() => {
37
+ if (!hasCustomColors) return undefined;
38
+
39
+ // Filter out segments without colors
40
+ const filtered = filterElementHasNoColor(seriesConfig || []);
41
+
42
+ // Validate: number of custom colors must match number of data series
43
+ if (filtered.length < data.length) {
44
+ throw new Error(ERROR_COLOR_NUMBER_MISMATCH);
45
+ }
46
+
47
+ // Validate: all data series must have corresponding custom colors
48
+ const dataLabels = new Set(data.map((d) => d.label));
49
+ if (!filtered.every((series) => dataLabels.has(series.label))) {
50
+ throw new Error(ERROR_COLOR_NUMBER_MISMATCH);
51
+ }
52
+
53
+ return filtered;
54
+ }, [seriesConfig, hasCustomColors, data]);
55
+
56
+ // Map custom segments to actual color values from palette
57
+ const customColors = useMemo(() => {
58
+ if (!customSegments) return [];
59
+
60
+ // Convert color keys to actual color values
61
+ const colors = customSegments.map((series) => {
62
+ const color = mobileVisualisationPalette[series.color];
63
+ // Validate: color key must exist in palette
64
+ if (color === undefined) {
65
+ throw new Error(ERROR_COLOR_NOT_FOUND.replace('{color}', series.color));
66
+ }
67
+ return color;
68
+ });
69
+
70
+ return colors;
71
+ }, [customSegments]);
72
+
73
+ // Create color scale:
74
+ // - If custom colors exist, use custom color mapping
75
+ // - Otherwise, fall back to default color mapping
76
+ return useColorScale(
77
+ customSegments
78
+ ? customSegments.map((series) => series.label)
79
+ : data.map((series) => series.label),
80
+ customColors
81
+ );
82
+ };
83
+
84
+ export default useCustomColor;
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Filter elements that have no color.
3
+ * @param data - The data to filter.
4
+ * @returns The filtered data.
5
+ */
6
+ const filterElementHasNoColor = <D extends object>(
7
+ data: Array<D & { color?: string }>
8
+ ): Array<D & { color: string }> => {
9
+ return data.filter((element) => element.color !== undefined) as Array<
10
+ D & { color: string }
11
+ >;
12
+ };
13
+
14
+ export { filterElementHasNoColor };
@@ -89,6 +89,37 @@ type HeaderConfig = {
89
89
  actionsExtra?: React.ReactNode;
90
90
  };
91
91
 
92
+ type ChartColor =
93
+ | 'primaryLight'
94
+ | 'blueLight'
95
+ | 'greenLight'
96
+ | 'redLight'
97
+ | 'orangeLight'
98
+ | 'yellowLight'
99
+ | 'pinkLight'
100
+ | 'greyLight'
101
+ | 'primaryMedium'
102
+ | 'blueMedium'
103
+ | 'greenMedium'
104
+ | 'redMedium'
105
+ | 'orangeMedium'
106
+ | 'yellowMedium'
107
+ | 'pinkMedium'
108
+ | 'greyMedium';
109
+
110
+ /**
111
+ * Represents the configuration for the style of the chart.
112
+ */
113
+ type StyleConfig = {
114
+ /**
115
+ * Chart colors config.
116
+ */
117
+ series?: Array<{
118
+ label: string;
119
+ color?: ChartColor;
120
+ }>;
121
+ };
122
+
92
123
  export type {
93
124
  Series,
94
125
  TickConfig,
@@ -97,4 +128,5 @@ export type {
97
128
  DataValue,
98
129
  AxisCoordinates,
99
130
  HeaderConfig,
131
+ StyleConfig,
100
132
  };