Skip to content

Commit 8bafe77

Browse files
celineungrevolunet
authored andcommitted
add Tooltip component
1 parent cc6dae4 commit 8bafe77

File tree

2 files changed

+157
-0
lines changed

2 files changed

+157
-0
lines changed

src/Tooltip.tsx

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import React, { forwardRef, memo, ReactElement } from "react";
2+
import type { Equals } from "tsafe";
3+
import { assert } from "tsafe/assert";
4+
import { symToStr } from "tsafe/symToStr";
5+
import { useAnalyticsId } from "./tools/useAnalyticsId";
6+
7+
export type TooltipProps = TooltipProps.WithClickAction | TooltipProps.WithHoverAction;
8+
9+
export namespace TooltipProps {
10+
export type Common = {
11+
description: string;
12+
id?: string;
13+
className?: string;
14+
};
15+
16+
export type WithClickAction = Common & {
17+
kind: "click";
18+
children?: ReactElement | string;
19+
};
20+
21+
export type WithHoverAction = Common & {
22+
kind?: "hover";
23+
children: ReactElement | string;
24+
};
25+
}
26+
27+
/** @see <https://components.react-dsfr.codegouv.studio/?path=/docs/components-tooltip> */
28+
export const Tooltip = memo(
29+
forwardRef<HTMLSpanElement, TooltipProps>((props, ref) => {
30+
const { id: id_prop, className, description, kind, children, ...rest } = props;
31+
assert<Equals<keyof typeof rest, never>>();
32+
33+
const id = useAnalyticsId({
34+
"defaultIdPrefix": "fr-tooltip",
35+
"explicitlyProvidedId": id_prop
36+
});
37+
38+
const displayChildren = (
39+
children: ReactElement | string | undefined,
40+
id: string
41+
): ReactElement => {
42+
if (children === undefined) return <></>;
43+
return typeof children === "string" ? (
44+
<span aria-describedby={id} id={`tooltip-owner-${id}`}>
45+
{children}
46+
</span>
47+
) : (
48+
children &&
49+
React.cloneElement(children, {
50+
"aria-describedby": id,
51+
"id": `tooltip-owner-${id}`
52+
})
53+
);
54+
};
55+
56+
return (
57+
<>
58+
{props.kind === "click" ? (
59+
<span ref={ref}>
60+
{children === undefined ? (
61+
<button
62+
className="fr-btn--tooltip fr-btn"
63+
aria-describedby={id}
64+
id={`tooltip-owner-${id}`}
65+
>
66+
Information contextuelle
67+
</button>
68+
) : (
69+
displayChildren(children, id)
70+
)}
71+
<span
72+
className={`fr-tooltip fr-placement ${props.className}`}
73+
id={id}
74+
role="tooltip"
75+
aria-hidden="true"
76+
>
77+
{props.description}
78+
</span>
79+
</span>
80+
) : (
81+
<span ref={ref}>
82+
{displayChildren(children, id)}
83+
<span
84+
className={`fr-tooltip fr-placement ${props.className}`}
85+
id={id}
86+
role="tooltip"
87+
aria-hidden="true"
88+
>
89+
{props.description}
90+
</span>
91+
</span>
92+
)}
93+
</>
94+
);
95+
})
96+
);
97+
98+
Tooltip.displayName = symToStr({ Tooltip });
99+
100+
export default Tooltip;

stories/Tooltip.stories.tsx

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { Tooltip, type TooltipProps } from "../dist/Tooltip";
2+
import { sectionName } from "./sectionName";
3+
import { getStoryFactory } from "./getStory";
4+
import { assert, Equals } from "tsafe/assert";
5+
6+
const { meta, getStory } = getStoryFactory({
7+
sectionName,
8+
"wrappedComponent": { Tooltip },
9+
"description": `
10+
- [See DSFR documentation](https://www.systeme-de-design.gouv.fr/elements-d-interface/composants/infobulle)
11+
- [See source code](https://github.com/codegouvfr/react-dsfr/blob/main/src/Tooltip.tsx)`,
12+
"argTypes": {
13+
"id": {
14+
"control": { "type": "text" },
15+
"description":
16+
"Optional: tootlip Id, which is also use as aria-describedby for hovered/clicked element"
17+
},
18+
"className": {
19+
"control": { "type": "text" },
20+
"description": "Optional"
21+
},
22+
"kind": {
23+
"control": { "type": "select" },
24+
"options": (() => {
25+
const options = ["hover", "click"] as const;
26+
27+
assert<Equals<typeof options[number] | undefined, TooltipProps["kind"]>>();
28+
29+
return options;
30+
})(),
31+
"description": "Optional."
32+
},
33+
"description": {
34+
"control": { "type": "text" }
35+
},
36+
"children": {
37+
"control": { "type": "text" }
38+
}
39+
},
40+
"disabledProps": ["lang"]
41+
});
42+
43+
export default meta;
44+
45+
const defaultOnHoverProps: TooltipProps.WithHoverAction = {
46+
"description": "lorem ipsum",
47+
"children": "Exemple"
48+
};
49+
50+
export const Default = getStory(defaultOnHoverProps);
51+
52+
export const TooltipOnHover = getStory(defaultOnHoverProps);
53+
54+
export const TooltipOnClick = getStory({
55+
"kind": "click",
56+
"description": "lorem ipsum"
57+
});

0 commit comments

Comments
 (0)