@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.
- package/.turbo/turbo-build.log +3 -3
- package/CHANGELOG.md +6 -0
- package/es/index.js +109 -25
- package/lib/index.js +109 -25
- package/package.json +1 -1
- package/src/components/Chart/ColumnChart/ColumnChartContent.tsx +19 -3
- package/src/components/Chart/ColumnChart/Segment.tsx +1 -1
- package/src/components/Chart/ColumnChart/StackedSegment.tsx +10 -6
- package/src/components/Chart/ColumnChart/__tests__/Segment.spec.tsx +1 -1
- package/src/components/Chart/ColumnChart/__tests__/__snapshots__/StackedSegment.spec.tsx.snap +6 -21
- package/src/components/Chart/ColumnChart/__tests__/__snapshots__/index.spec.tsx.snap +999 -6
- package/src/components/Chart/ColumnChart/__tests__/index.spec.tsx +107 -0
- package/src/components/Chart/ColumnChart/index.tsx +15 -0
- package/src/components/Chart/Line/Line.tsx +5 -2
- package/src/components/Chart/Line/__tests__/Line.spec.tsx +13 -6
- package/src/components/Chart/Line/__tests__/__snapshots__/Line.spec.tsx.snap +1 -1
- package/src/components/Chart/Line/__tests__/__snapshots__/index.spec.tsx.snap +1464 -4
- package/src/components/Chart/Line/__tests__/index.spec.tsx +95 -1
- package/src/components/Chart/Line/index.tsx +14 -2
- package/src/components/Chart/shared/__tests__/utils.spec.ts +16 -0
- package/src/components/Chart/shared/constants.ts +4 -0
- package/src/components/Chart/shared/hooks/useCustomColor.ts +84 -0
- package/src/components/Chart/shared/utils.ts +14 -0
- package/src/components/Chart/types.ts +32 -0
- package/stats/8.100.2/rn-stats.html +1 -3
- package/stats/8.101.0/rn-stats.html +4844 -0
- package/types/components/Chart/ColumnChart/ColumnChartContent.d.ts +5 -1
- package/types/components/Chart/ColumnChart/StackedSegment.d.ts +4 -0
- package/types/components/Chart/ColumnChart/index.d.ts +8 -2
- package/types/components/Chart/Line/Line.d.ts +3 -1
- package/types/components/Chart/Line/index.d.ts +8 -2
- package/types/components/Chart/index.d.ts +2 -2
- package/types/components/Chart/shared/constants.d.ts +2 -0
- package/types/components/Chart/shared/hooks/useCustomColor.d.ts +22 -0
- package/types/components/Chart/shared/utils.d.ts +11 -0
- 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
|
|
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 =
|
|
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
|
};
|