diff --git a/src/Tooltip.tsx b/src/Tooltip.tsx new file mode 100644 index 000000000..713aa3a95 --- /dev/null +++ b/src/Tooltip.tsx @@ -0,0 +1,105 @@ +import React, { forwardRef, memo } from "react"; +import type { ReactNode, CSSProperties } from "react"; +import type { Equals } from "tsafe"; +import { assert } from "tsafe/assert"; +import { symToStr } from "tsafe/symToStr"; +import { useAnalyticsId } from "./tools/useAnalyticsId"; +import { createComponentI18nApi } from "./i18n"; +import { fr } from "./fr"; +import { cx } from "./tools/cx"; + +export type TooltipProps = TooltipProps.WithClickAction | TooltipProps.WithHoverAction; + +export namespace TooltipProps { + export type Common = { + title: ReactNode; + id?: string; + className?: string; + style?: CSSProperties; + }; + + export type WithClickAction = Common & { + kind: "click"; + children?: undefined; + }; + + export type WithHoverAction = Common & { + kind?: "hover"; + children?: ReactNode; + }; +} + +/** @see */ +export const Tooltip = memo( + forwardRef((props, ref) => { + const { id: id_prop, className, title, kind, style, children, ...rest } = props; + assert>(); + + const { t } = useTranslation(); + + const id = useAnalyticsId({ + "defaultIdPrefix": "fr-tooltip", + "explicitlyProvidedId": id_prop + }); + + const TooltipSpan = () => ( + + ); + + return ( + <> + {kind === "click" ? ( + + ) : typeof children === "undefined" ? ( + // mimic default tooltip style + + ) : ( + + {children} + + )} + + + ); + }) +); + +Tooltip.displayName = symToStr({ Tooltip }); + +const { useTranslation, addTooltipTranslations } = createComponentI18nApi({ + "componentName": symToStr({ Tooltip }), + "frMessages": { + "tooltip-button-text": "Information contextuelle" + } +}); + +addTooltipTranslations({ + "lang": "en", + "messages": { + "tooltip-button-text": "Contextual information" + } +}); + +export { addTooltipTranslations }; + +export default Tooltip; diff --git a/stories/Tooltip.stories.tsx b/stories/Tooltip.stories.tsx new file mode 100644 index 000000000..b1e42c88f --- /dev/null +++ b/stories/Tooltip.stories.tsx @@ -0,0 +1,64 @@ +import React from "react"; +import { assert, Equals } from "tsafe/assert"; + +import { Tooltip, type TooltipProps } from "../src/Tooltip"; + +import { sectionName } from "./sectionName"; +import { getStoryFactory } from "./getStory"; + +const { meta, getStory } = getStoryFactory({ + sectionName, + "wrappedComponent": { Tooltip }, + "description": ` +- [See DSFR documentation](https://www.systeme-de-design.gouv.fr/elements-d-interface/composants/infobulle) +- [See source code](https://github.com/codegouvfr/react-dsfr/blob/main/src/Tooltip.tsx)`, + "argTypes": { + "id": { + "control": { "type": "text" }, + "description": + "Optional: tootlip Id, which is also use as aria-describedby for hovered/clicked element" + }, + "className": { + "control": { "type": "text" }, + "description": "Optional" + }, + "kind": { + "control": { "type": "select" }, + "options": (() => { + const options = ["hover", "click"] as const; + + assert>(); + + return options; + })(), + "description": "Optional." + }, + "title": { + "control": { "type": "text" } + }, + "children": { + "control": { "type": "text" } + } + } +}); + +export default meta; + +const defaultOnHoverProps: TooltipProps.WithHoverAction = { + "kind": "hover", + "title": "lorem ipsum" +}; + +export const Default = getStory(defaultOnHoverProps); + +export const TooltipOnHover = getStory(defaultOnHoverProps); + +export const TooltipOnHoverWithChild = getStory({ + ...defaultOnHoverProps, + children: Some link +}); + +export const TooltipOnClick = getStory({ + "kind": "click", + "title": "lorem ipsum" +}); diff --git a/test/types/Tooltip.tsx b/test/types/Tooltip.tsx new file mode 100644 index 000000000..aff22fddc --- /dev/null +++ b/test/types/Tooltip.tsx @@ -0,0 +1,18 @@ +import { Tooltip } from "../../src/Tooltip"; + +{ + Exemple; +} +{ + + Exemple + ; +} +{ + ; +} +{ + + Exemple + ; +}