Skip to content

Commit

Permalink
Use composable tooltip, revert DefaultTooltip changes
Browse files Browse the repository at this point in the history
  • Loading branch information
pyyding committed May 7, 2024
1 parent 195cafb commit 4c28d20
Show file tree
Hide file tree
Showing 8 changed files with 83 additions and 79 deletions.
18 changes: 18 additions & 0 deletions lib/components/ChartTooltip/Content.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { cx } from "../common/utils";

type ContentProps = React.HTMLProps<HTMLDivElement>;

export const ChartTooltipContent: React.FC<ContentProps> = ({
children,
className,
...rest
}) => {
return (
<div
className={cx(className, "bg-gray-900 px-3 py-2 rounded-md")}
{...rest}
>
{children}
</div>
);
};
26 changes: 9 additions & 17 deletions lib/components/ChartTooltip/DefaultTooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,18 @@ import { ChartTooltipValue } from "./Value";
import { ChartTooltipTitle } from "./Title";
import { ChartTooltipFooter } from "./Footer";
import { Payload as RechartsTooltipPayload } from "recharts/types/component/DefaultTooltipContent";
import { ChartTooltipContent } from "./Content.tsx";

export type TooltipFullPayload = RechartsTooltipPayload<any, any>;
type TooltipProps = RechartsTooltipProps<any, any>;
type Payload = TooltipFullPayload["payload"];

type LabelWithFormatter = string | ((payload: Payload) => string);

type DefaultTooltipProps = TooltipProps & {
label: LabelWithFormatter;
valueFormatter: (payload: Payload, fullPayload: TooltipFullPayload) => string;
interface DefaultTooltipProps extends TooltipProps {
label: string;
valueFormatter: (payload: Payload) => string;
footerFormatter?: (payload: Payload) => string;
active?: boolean;
};
}

export const DefaultTooltip: React.FC<DefaultTooltipProps> = ({
label,
Expand All @@ -27,24 +26,17 @@ export const DefaultTooltip: React.FC<DefaultTooltipProps> = ({
const firstPayload = payload?.[0];
if (!active || !firstPayload) return null;

const labelText = getLabelText(label, firstPayload.payload);

return (
<div className="bg-gray-900 px-3 py-2 rounded-md">
<ChartTooltipTitle>{labelText}</ChartTooltipTitle>
<ChartTooltipContent>
<ChartTooltipTitle>{label}</ChartTooltipTitle>
{payload?.map((p, index) => (
<div key={index}>
<ChartTooltipValue value={valueFormatter(p.payload, p)} />
<ChartTooltipValue>{valueFormatter(p.payload)}</ChartTooltipValue>
{footerFormatter && (
<ChartTooltipFooter subtitle={footerFormatter(p.payload)} />
)}
</div>
))}
</div>
</ChartTooltipContent>
);
};

function getLabelText(label: LabelWithFormatter, payload: Payload): string {
if (typeof label === "string") return label;
return label(payload);
}
14 changes: 12 additions & 2 deletions lib/components/ChartTooltip/Title.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
export const ChartTooltipTitle: React.FC<{ children: React.ReactNode }> = ({ children }) => (
<p className="text-xs text-gray-400">{children}</p>
import { cx } from "../common/utils.ts";

type ChartTooltipTitleProps = React.HTMLProps<HTMLParagraphElement>;

export const ChartTooltipTitle: React.FC<ChartTooltipTitleProps> = ({
children,
className,
...rest
}) => (
<p className={cx(className, "text-xs text-gray-400")} {...rest}>
{children}
</p>
);
16 changes: 13 additions & 3 deletions lib/components/ChartTooltip/Value.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
export const ChartTooltipValue: React.FC<{ value: string }> = ({ value }) => (
<p className="text-sm text-white">{value}</p>
);
import { cx } from "../common/utils.ts";

type ChartTooltipValueProps = React.HTMLProps<HTMLParagraphElement>;

export const ChartTooltipValue: React.FC<ChartTooltipValueProps> = ({
children,
className,
...rest
}) => (
<p className={cx(className, "text-sm text-white gap-1 flex")} {...rest}>
{children}
</p>
);
2 changes: 2 additions & 0 deletions lib/components/ChartTooltip/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ import { ChartTooltipValue } from "./Value";
import { ChartTooltipTitle } from "./Title";
import { ChartTooltipFooter } from "./Footer";
import { DefaultTooltip } from "./DefaultTooltip";
import { ChartTooltipContent } from "./Content";

export {
ChartTooltip,
ChartTooltipTitle,
ChartTooltipValue,
ChartTooltipFooter,
DefaultTooltip,
ChartTooltipContent,
type ChartTooltipProps,
};
1 change: 1 addition & 0 deletions lib/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export {
ChartTooltipTitle,
ChartTooltipValue,
ChartTooltipFooter,
ChartTooltipContent,
type ChartTooltipProps,
} from "./components/ChartTooltip";
export {
Expand Down
45 changes: 28 additions & 17 deletions src/stories/ScatterChart.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import {
ChartCell,
ChartTooltip,
defaultScatterProps,
DefaultTooltip,
imagineColor,
ReferenceLine,
Scatter,
Expand All @@ -18,6 +17,9 @@ import {
scatterYAxisProps,
scatterXAxisProps,
scatterChartCellProps,
ChartTooltipContent,
ChartTooltipTitle,
ChartTooltipValue,
} from "../../lib/main";

import type { Meta, StoryObj } from "@storybook/react";
Expand All @@ -35,12 +37,6 @@ export default meta;

type Story = StoryObj<typeof ScatterChart>;

type ScatterData = {
name: string;
frequency: number;
popularity: number;
};

const data = [
{
name: "Product A",
Expand Down Expand Up @@ -77,16 +73,31 @@ export const Default: Story = {
<Grid {...scatterGridProps} />
<ChartTooltip
{...scatterChartTooltipProps}
content={({ active, payload }) => (
<DefaultTooltip
label={(payload: ScatterData) => payload.name}
active={active}
payload={payload}
valueFormatter={(_, fullPayload) => {
return `${fullPayload.name}: ${fullPayload.value}${fullPayload.unit}`;
}}
/>
)}
content={(props) => {
const { payload } = props;
const firstPayload = payload?.[0];

if (!firstPayload) {
console.error("No payload found in ScatterChart tooltip");
return;
}
return (
<ChartTooltipContent>
<ChartTooltipTitle className="text-white font-bold">
{firstPayload.payload.name}
</ChartTooltipTitle>
{payload?.map((p) => (
<ChartTooltipValue key={p.dataKey}>
<span className="text-xs text-gray-400">{p.name}:</span>
<span className="text-xs text-bold">
{p.value}
{p.unit}
</span>
</ChartTooltipValue>
))}
</ChartTooltipContent>
);
}}
/>
<ReferenceLine {...scatterReferenceLineYProps} />
<ReferenceLine {...scatterReferenceLineXProps} />
Expand Down
40 changes: 0 additions & 40 deletions src/tests/components/DefaultTooltip.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,44 +46,4 @@ describe("DefaultTooltip", () => {
expect(screen.getByText("$100")).toBeInTheDocument();
expect(screen.getByText("Sub: 100")).toBeInTheDocument();
});

it("should format values using full payload", () => {
const payload = { popularity: 12, frequency: 13 };
const fullPayload = [
{ name: "Popularity", value: 12, unit: "%", payload },
{ name: "Frequency", value: 13, unit: "%", payload },
];
render(
<DefaultTooltip
label="Test Label"
payload={fullPayload}
active={true}
valueFormatter={(_, fullPayload) => {
return `${fullPayload.name}: ${fullPayload.value}${fullPayload.unit}`;
}}
/>,
);

expect(screen.getByText("Popularity: 12%")).toBeInTheDocument();
expect(screen.getByText("Frequency: 13%")).toBeInTheDocument();
});

it("should format label when label is a function", () => {
const payload = [
{
value: 123,
payload: { name: "test-entry-name" },
},
];
render(
<DefaultTooltip
label={(p: { name: string }) => `${p.name}`}
payload={payload}
active={true}
valueFormatter={(p: { value: number }) => `${p.value}`}
/>,
);

expect(screen.getByText("test-entry-name")).toBeInTheDocument();
});
});

0 comments on commit 4c28d20

Please sign in to comment.