Skip to content

Commit c1c3daf

Browse files
authored
feat: Adds details renderer to core tooltip API (#69)
* feat: Adds details renderer to core tooltip API * add core tooltip content tests
1 parent 6f249e2 commit c1c3daf

File tree

6 files changed

+75
-33
lines changed

6 files changed

+75
-33
lines changed

src/__tests__/__snapshots__/documenter.test.ts.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -883,7 +883,7 @@ Supported series types:
883883
{
884884
"name": "details",
885885
"optional": true,
886-
"type": "((props: TooltipSlotRenderProps) => ReadonlyArray<PieChartProps.TooltipDetail>)",
886+
"type": "((props: TooltipSlotRenderProps) => ReadonlyArray<BaseTooltipDetail>)",
887887
},
888888
{
889889
"name": "enabled",

src/core/__tests__/chart-core-tooltip.test.tsx

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { waitFor } from "@testing-library/react";
66
import highcharts from "highcharts";
77
import { vi } from "vitest";
88

9-
import { CoreChartAPI } from "../../../lib/components/core/interfaces";
9+
import { CoreChartProps } from "../../../lib/components/core/interfaces";
1010
import testClasses from "../../../lib/components/core/test-classes/styles.selectors";
1111
import { createChartWrapper, renderChart } from "./common";
1212
import { HighchartsTestHelper } from "./highcharts-utils";
@@ -51,7 +51,7 @@ const data = [
5151
{ name: "P3", y: 60 },
5252
];
5353

54-
const series: Highcharts.SeriesOptionsType[] = [
54+
const pieSeries: Highcharts.SeriesOptionsType[] = [
5555
{
5656
type: "pie",
5757
name: "Pie series",
@@ -84,7 +84,7 @@ describe("CoreChart: tooltip", () => {
8484
test("renders highcharts tooltip", () => {
8585
const { wrapper } = renderChart({
8686
highcharts,
87-
options: { series, tooltip: { enabled: true, formatter: () => "Custom content" } },
87+
options: { series: pieSeries, tooltip: { enabled: true, formatter: () => "Custom content" } },
8888
tooltip: { enabled: false },
8989
});
9090

@@ -184,12 +184,12 @@ describe("CoreChart: tooltip", () => {
184184
});
185185

186186
test("shows tooltip with api", async () => {
187-
let api: null | CoreChartAPI = null;
187+
let api: null | CoreChartProps.ChartAPI = null;
188188
const onHighlight = vi.fn();
189189
const onClearHighlight = vi.fn();
190190
const { wrapper } = renderChart({
191191
highcharts,
192-
options: { series },
192+
options: { series: pieSeries },
193193
onHighlight,
194194
onClearHighlight,
195195
getTooltipContent: () => ({
@@ -221,7 +221,7 @@ describe("CoreChart: tooltip", () => {
221221
test("keeps showing tooltip when cursor is over the tooltip", async () => {
222222
const { wrapper } = renderChart({
223223
highcharts,
224-
options: { series },
224+
options: { series: pieSeries },
225225
getTooltipContent: () => ({ header: () => "", body: () => "" }),
226226
});
227227

@@ -250,7 +250,7 @@ describe("CoreChart: tooltip", () => {
250250
test("pins and unpins tooltip", async () => {
251251
const { wrapper } = renderChart({
252252
highcharts,
253-
options: { series },
253+
options: { series: pieSeries },
254254
getTooltipContent: ({ point }) => ({ header: () => `y${point?.y}`, body: () => "" }),
255255
});
256256

@@ -291,7 +291,7 @@ describe("CoreChart: tooltip", () => {
291291
test("provides point and group for onHighlight and getTooltipContent", async () => {
292292
const onHighlight = vi.fn();
293293
const getTooltipContent = vi.fn();
294-
renderChart({ highcharts, options: { series }, onHighlight, getTooltipContent });
294+
renderChart({ highcharts, options: { series: pieSeries }, onHighlight, getTooltipContent });
295295

296296
for (let i = 0; i < data.length; i++) {
297297
act(() => hc.highlightChartPoint(0, i));
@@ -370,4 +370,39 @@ describe("CoreChart: tooltip", () => {
370370
expect(onHighlight).toHaveBeenCalledWith(expect.objectContaining({ point: hc.getChartPoint(0, 1) }));
371371
},
372372
);
373+
374+
test("renders customized cartesian points", () => {
375+
const { wrapper } = renderChart({
376+
highcharts,
377+
options: { series: lineSeries },
378+
getTooltipContent: () => ({
379+
header: () => "Header",
380+
point: ({ item }) => ({ key: ` [${item.point.series.name}]`, value: ` [${item.point.y}]` }),
381+
}),
382+
});
383+
384+
act(() => hc.highlightChartPoint(0, 2));
385+
386+
expect(wrapper.findTooltip()!.findBody()!.getElement().textContent).toBe(
387+
" [Line series 1] [13] [Line series 2] [23]",
388+
);
389+
});
390+
391+
test("renders customized pie segment details", () => {
392+
const { wrapper } = renderChart({
393+
highcharts,
394+
options: { series: pieSeries },
395+
getTooltipContent: () => ({
396+
header: () => "Header",
397+
details: ({ point }) => [
398+
{ key: `[${point.name}]`, value: ` [${point.y}]` },
399+
{ key: " [custom key]", value: " [custom value]" },
400+
],
401+
}),
402+
});
403+
404+
act(() => hc.highlightChartPoint(0, 2));
405+
406+
expect(wrapper.findTooltip()!.findBody()!.getElement().textContent).toBe("[P3] [60] [custom key] [custom value]");
407+
});
373408
});

src/core/components/core-tooltip.tsx

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -215,11 +215,15 @@ function getTooltipContentPie(
215215
</Box>
216216
</div>
217217
),
218-
body: renderers.body?.(tooltipDetails) ?? (
219-
// We expect all pie chart segments to have defined y values. We use y=0 as fallback
220-
// because the property is optional in Highcharts types.
221-
<ChartSeriesDetails details={[{ key: point.series.name, value: point.y ?? 0 }]} />
222-
),
218+
body:
219+
renderers.body?.(tooltipDetails) ??
220+
(renderers.details ? (
221+
<ChartSeriesDetails details={renderers.details({ point })} compactList={true} />
222+
) : (
223+
// We expect all pie chart segments to have defined y values. We use y=0 as fallback
224+
// because the property is optional in Highcharts types.
225+
<ChartSeriesDetails details={[{ key: point.series.name, value: point.y ?? 0 }]} />
226+
)),
223227
footer: renderers.footer?.(tooltipDetails),
224228
};
225229
}

src/core/interfaces.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,11 @@ export interface BaseNoDataOptions {
108108
onRecoveryClick?: NonCancelableEventHandler;
109109
}
110110

111+
export interface BaseTooltipDetail {
112+
key: React.ReactNode;
113+
value: React.ReactNode;
114+
}
115+
111116
export interface BaseI18nStrings {
112117
loadingText?: string;
113118
errorText?: string;
@@ -428,6 +433,7 @@ export namespace CoreChartProps {
428433
}
429434
export interface TooltipContentRenderer {
430435
point?: (props: TooltipPointProps) => TooltipPointFormatted;
436+
details?: (props: TooltipDetailsProps) => readonly TooltipDetail[];
431437
header?: (props: TooltipSlotProps) => React.ReactNode;
432438
body?: (props: TooltipSlotProps) => React.ReactNode;
433439
footer?: (props: TooltipSlotProps) => React.ReactNode;
@@ -445,6 +451,12 @@ export namespace CoreChartProps {
445451
items: TooltipContentItem[];
446452
}
447453

454+
export interface TooltipDetailsProps {
455+
point: Highcharts.Point;
456+
}
457+
458+
export type TooltipDetail = BaseTooltipDetail;
459+
448460
export interface LegendItemHighlightDetail {
449461
item: LegendItem;
450462
}

src/pie-chart/chart-pie-internal.tsx

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import { CoreChartProps } from "../core/interfaces";
1212
import { getOptionsId } from "../core/utils";
1313
import { InternalBaseComponentProps } from "../internal/base-component/use-base-component";
1414
import * as Styles from "../internal/chart-styles";
15-
import ChartSeriesDetails from "../internal/components/series-details";
1615
import { fireNonCancelableEvent } from "../internal/events";
1716
import { SomeRequired, Writeable } from "../internal/utils/utils";
1817
import { useInnerArea } from "./chart-inner-area";
@@ -62,29 +61,24 @@ export const InternalPieChart = forwardRef(
6261
// We convert pie tooltip options to the core chart's getTooltipContent callback,
6362
// ensuring no internal types are exposed to the consumer-defined render functions.
6463
const getTooltipContent: CoreChartProps["getTooltipContent"] = () => {
65-
const transformSlotProps = (props: CoreChartProps.TooltipSlotProps): PieChartProps.TooltipDetailsRenderProps => {
66-
const point = props.items[0].point;
64+
const transformDetailsProps = ({
65+
point,
66+
}: CoreChartProps.TooltipDetailsProps): PieChartProps.TooltipDetailsRenderProps => {
6767
return {
6868
totalValue: point.total ?? 0,
6969
segmentValue: point.y ?? 0,
7070
segmentId: getOptionsId(point.options),
7171
segmentName: point.name ?? "",
7272
};
7373
};
74+
const transformSlotProps = (props: CoreChartProps.TooltipSlotProps): PieChartProps.TooltipDetailsRenderProps => {
75+
const point = props.items[0].point;
76+
return transformDetailsProps({ point });
77+
};
7478
return {
7579
header: tooltip?.header ? (props) => tooltip.header!(transformSlotProps(props)) : undefined,
76-
body:
77-
tooltip?.body || tooltip?.details
78-
? (props) =>
79-
tooltip.body ? (
80-
tooltip.body(transformSlotProps(props))
81-
) : (
82-
<ChartSeriesDetails
83-
details={tooltip?.details?.(transformSlotProps(props)) ?? []}
84-
compactList={true}
85-
/>
86-
)
87-
: undefined,
80+
details: tooltip?.details ? (props) => tooltip.details!(transformDetailsProps(props)) : undefined,
81+
body: tooltip?.body ? (props) => tooltip.body!(transformSlotProps(props)) : undefined,
8882
footer: tooltip?.footer ? (props) => tooltip.footer!(transformSlotProps(props)) : undefined,
8983
};
9084
};

src/pie-chart/interfaces.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -105,10 +105,7 @@ export namespace PieChartProps {
105105
totalValue: number;
106106
}
107107

108-
export interface TooltipDetail {
109-
key: React.ReactNode;
110-
value: React.ReactNode;
111-
}
108+
export type TooltipDetail = CoreTypes.BaseTooltipDetail;
112109

113110
export type SegmentTitleRenderProps = SegmentDescriptionRenderProps;
114111
export interface SegmentDescriptionRenderProps {

0 commit comments

Comments
 (0)