From 8bafe777a5e5783ac0b901c395444076995cd085 Mon Sep 17 00:00:00 2001 From: celineung Date: Fri, 25 Aug 2023 17:46:02 +0200 Subject: [PATCH 1/5] add Tooltip component --- src/Tooltip.tsx | 100 ++++++++++++++++++++++++++++++++++++ stories/Tooltip.stories.tsx | 57 ++++++++++++++++++++ 2 files changed, 157 insertions(+) create mode 100644 src/Tooltip.tsx create mode 100644 stories/Tooltip.stories.tsx diff --git a/src/Tooltip.tsx b/src/Tooltip.tsx new file mode 100644 index 00000000..224139dd --- /dev/null +++ b/src/Tooltip.tsx @@ -0,0 +1,100 @@ +import React, { forwardRef, memo, ReactElement } from "react"; +import type { Equals } from "tsafe"; +import { assert } from "tsafe/assert"; +import { symToStr } from "tsafe/symToStr"; +import { useAnalyticsId } from "./tools/useAnalyticsId"; + +export type TooltipProps = TooltipProps.WithClickAction | TooltipProps.WithHoverAction; + +export namespace TooltipProps { + export type Common = { + description: string; + id?: string; + className?: string; + }; + + export type WithClickAction = Common & { + kind: "click"; + children?: ReactElement | string; + }; + + export type WithHoverAction = Common & { + kind?: "hover"; + children: ReactElement | string; + }; +} + +/** @see */ +export const Tooltip = memo( + forwardRef((props, ref) => { + const { id: id_prop, className, description, kind, children, ...rest } = props; + assert>(); + + const id = useAnalyticsId({ + "defaultIdPrefix": "fr-tooltip", + "explicitlyProvidedId": id_prop + }); + + const displayChildren = ( + children: ReactElement | string | undefined, + id: string + ): ReactElement => { + if (children === undefined) return <>; + return typeof children === "string" ? ( + + {children} + + ) : ( + children && + React.cloneElement(children, { + "aria-describedby": id, + "id": `tooltip-owner-${id}` + }) + ); + }; + + return ( + <> + {props.kind === "click" ? ( + + {children === undefined ? ( + + ) : ( + displayChildren(children, id) + )} + + + ) : ( + + {displayChildren(children, id)} + + + )} + + ); + }) +); + +Tooltip.displayName = symToStr({ Tooltip }); + +export default Tooltip; diff --git a/stories/Tooltip.stories.tsx b/stories/Tooltip.stories.tsx new file mode 100644 index 00000000..a4afa037 --- /dev/null +++ b/stories/Tooltip.stories.tsx @@ -0,0 +1,57 @@ +import { Tooltip, type TooltipProps } from "../dist/Tooltip"; +import { sectionName } from "./sectionName"; +import { getStoryFactory } from "./getStory"; +import { assert, Equals } from "tsafe/assert"; + +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." + }, + "description": { + "control": { "type": "text" } + }, + "children": { + "control": { "type": "text" } + } + }, + "disabledProps": ["lang"] +}); + +export default meta; + +const defaultOnHoverProps: TooltipProps.WithHoverAction = { + "description": "lorem ipsum", + "children": "Exemple" +}; + +export const Default = getStory(defaultOnHoverProps); + +export const TooltipOnHover = getStory(defaultOnHoverProps); + +export const TooltipOnClick = getStory({ + "kind": "click", + "description": "lorem ipsum" +}); From df1c2b114daceafcf9f91fdc9118a5fff0d118bc Mon Sep 17 00:00:00 2001 From: celineung Date: Mon, 28 Aug 2023 12:46:06 +0200 Subject: [PATCH 2/5] add tooltip type test --- test/types/Tooltip.tsx | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 test/types/Tooltip.tsx diff --git a/test/types/Tooltip.tsx b/test/types/Tooltip.tsx new file mode 100644 index 00000000..aff22fdd --- /dev/null +++ b/test/types/Tooltip.tsx @@ -0,0 +1,18 @@ +import { Tooltip } from "../../src/Tooltip"; + +{ + Exemple; +} +{ + + Exemple + ; +} +{ + ; +} +{ + + Exemple + ; +} From a92141778810c239769d898ab753f6bfcf581928 Mon Sep 17 00:00:00 2001 From: Julien Bouquillon Date: Mon, 7 Oct 2024 22:01:36 +0200 Subject: [PATCH 3/5] fix(tooltip): add i18n --- src/Tooltip.tsx | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/Tooltip.tsx b/src/Tooltip.tsx index 224139dd..2a7b75ec 100644 --- a/src/Tooltip.tsx +++ b/src/Tooltip.tsx @@ -3,6 +3,7 @@ import type { Equals } from "tsafe"; import { assert } from "tsafe/assert"; import { symToStr } from "tsafe/symToStr"; import { useAnalyticsId } from "./tools/useAnalyticsId"; +import { createComponentI18nApi } from "./i18n"; export type TooltipProps = TooltipProps.WithClickAction | TooltipProps.WithHoverAction; @@ -30,6 +31,8 @@ export const Tooltip = memo( const { id: id_prop, className, description, kind, children, ...rest } = props; assert>(); + const { t } = useTranslation(); + const id = useAnalyticsId({ "defaultIdPrefix": "fr-tooltip", "explicitlyProvidedId": id_prop @@ -63,7 +66,7 @@ export const Tooltip = memo( aria-describedby={id} id={`tooltip-owner-${id}`} > - Information contextuelle + {t("tooltip-button-text")} ) : ( displayChildren(children, id) @@ -97,4 +100,20 @@ export const Tooltip = memo( 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; From 9f9ef216eb6d97f0346558fe9913e57c4c72db1d Mon Sep 17 00:00:00 2001 From: Julien Bouquillon Date: Mon, 7 Oct 2024 22:57:00 +0200 Subject: [PATCH 4/5] fix(tooltip): review fixes --- src/Tooltip.tsx | 88 ++++++++++++++----------------------- stories/Tooltip.stories.tsx | 17 ++++--- 2 files changed, 44 insertions(+), 61 deletions(-) diff --git a/src/Tooltip.tsx b/src/Tooltip.tsx index 2a7b75ec..1fcc359d 100644 --- a/src/Tooltip.tsx +++ b/src/Tooltip.tsx @@ -1,34 +1,38 @@ -import React, { forwardRef, memo, ReactElement } from "react"; +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 = { - description: string; + title: ReactNode; id?: string; className?: string; + style?: CSSProperties; }; export type WithClickAction = Common & { kind: "click"; - children?: ReactElement | string; + children?: undefined; }; export type WithHoverAction = Common & { kind?: "hover"; - children: ReactElement | string; + children: ReactNode; }; } /** @see */ export const Tooltip = memo( forwardRef((props, ref) => { - const { id: id_prop, className, description, kind, children, ...rest } = props; + const { id: id_prop, className, title, kind, style, children, ...rest } = props; assert>(); const { t } = useTranslation(); @@ -38,61 +42,35 @@ export const Tooltip = memo( "explicitlyProvidedId": id_prop }); - const displayChildren = ( - children: ReactElement | string | undefined, - id: string - ): ReactElement => { - if (children === undefined) return <>; - return typeof children === "string" ? ( - - {children} - - ) : ( - children && - React.cloneElement(children, { - "aria-describedby": id, - "id": `tooltip-owner-${id}` - }) - ); - }; + const TooltipSpan = () => ( + + ); return ( <> - {props.kind === "click" ? ( - - {children === undefined ? ( - - ) : ( - displayChildren(children, id) - )} - - - ) : ( - - {displayChildren(children, id)} - + {(kind === "click" && ( + + )) || ( + + {children} )} + ); }) diff --git a/stories/Tooltip.stories.tsx b/stories/Tooltip.stories.tsx index a4afa037..80ebc8c8 100644 --- a/stories/Tooltip.stories.tsx +++ b/stories/Tooltip.stories.tsx @@ -1,3 +1,4 @@ +import React from "react"; import { Tooltip, type TooltipProps } from "../dist/Tooltip"; import { sectionName } from "./sectionName"; import { getStoryFactory } from "./getStory"; @@ -30,28 +31,32 @@ const { meta, getStory } = getStoryFactory({ })(), "description": "Optional." }, - "description": { + "title": { "control": { "type": "text" } }, "children": { "control": { "type": "text" } } - }, - "disabledProps": ["lang"] + } }); export default meta; const defaultOnHoverProps: TooltipProps.WithHoverAction = { - "description": "lorem ipsum", - "children": "Exemple" + "title": "lorem ipsum", + "children": "Hover example" }; export const Default = getStory(defaultOnHoverProps); export const TooltipOnHover = getStory(defaultOnHoverProps); +export const TooltipOnHoverLink = getStory({ + ...defaultOnHoverProps, + children: Some link +}); + export const TooltipOnClick = getStory({ "kind": "click", - "description": "lorem ipsum" + "title": "lorem ipsum" }); From 0154782f68ab9256afb46c45597fb33a2ebf7928 Mon Sep 17 00:00:00 2001 From: Julien Bouquillon Date: Mon, 7 Oct 2024 23:30:39 +0200 Subject: [PATCH 5/5] fix --- src/Tooltip.tsx | 14 +++++++++++--- stories/Tooltip.stories.tsx | 12 +++++++----- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/Tooltip.tsx b/src/Tooltip.tsx index 1fcc359d..713aa3a9 100644 --- a/src/Tooltip.tsx +++ b/src/Tooltip.tsx @@ -25,7 +25,7 @@ export namespace TooltipProps { export type WithHoverAction = Common & { kind?: "hover"; - children: ReactNode; + children?: ReactNode; }; } @@ -57,7 +57,7 @@ export const Tooltip = memo( return ( <> - {(kind === "click" && ( + {kind === "click" ? ( - )) || ( + ) : typeof children === "undefined" ? ( + // mimic default tooltip style + + ) : ( {children} diff --git a/stories/Tooltip.stories.tsx b/stories/Tooltip.stories.tsx index 80ebc8c8..b1e42c88 100644 --- a/stories/Tooltip.stories.tsx +++ b/stories/Tooltip.stories.tsx @@ -1,8 +1,10 @@ import React from "react"; -import { Tooltip, type TooltipProps } from "../dist/Tooltip"; +import { assert, Equals } from "tsafe/assert"; + +import { Tooltip, type TooltipProps } from "../src/Tooltip"; + import { sectionName } from "./sectionName"; import { getStoryFactory } from "./getStory"; -import { assert, Equals } from "tsafe/assert"; const { meta, getStory } = getStoryFactory({ sectionName, @@ -43,15 +45,15 @@ const { meta, getStory } = getStoryFactory({ export default meta; const defaultOnHoverProps: TooltipProps.WithHoverAction = { - "title": "lorem ipsum", - "children": "Hover example" + "kind": "hover", + "title": "lorem ipsum" }; export const Default = getStory(defaultOnHoverProps); export const TooltipOnHover = getStory(defaultOnHoverProps); -export const TooltipOnHoverLink = getStory({ +export const TooltipOnHoverWithChild = getStory({ ...defaultOnHoverProps, children: Some link });