From 4fb2db1bd6c45bf776d432409deedde1126fe433 Mon Sep 17 00:00:00 2001 From: gabrieljablonski Date: Thu, 15 Dec 2022 17:50:16 -0300 Subject: [PATCH 01/19] feat: effect float POC --- src/components/Tooltip/Tooltip.tsx | 37 ++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/src/components/Tooltip/Tooltip.tsx b/src/components/Tooltip/Tooltip.tsx index 995bf3e9d..2b32c875a 100644 --- a/src/components/Tooltip/Tooltip.tsx +++ b/src/components/Tooltip/Tooltip.tsx @@ -108,6 +108,42 @@ const Tooltip = ({ } } + const handleMouseMove = (event?: MouseEvent) => { + if (!event) { + return + } + const virtualElement = { + getBoundingClientRect() { + return { + x: event.clientX, + y: event.clientY, + width: 0, + height: 0, + top: event.clientY, + left: event.clientX, + right: event.clientX, + bottom: event.clientY, + } + }, + } as Element + computeTooltipPosition({ + place, + offset, + elementReference: virtualElement, + tooltipReference: tooltipRef.current, + tooltipArrowReference: tooltipArrowRef.current, + strategy: positionStrategy, + }).then((computedStylesData) => { + setCalculatingPosition(false) + if (Object.keys(computedStylesData.tooltipStyles).length) { + setInlineStyles(computedStylesData.tooltipStyles) + } + if (Object.keys(computedStylesData.tooltipArrowStyles).length) { + setInlineArrowStyles(computedStylesData.tooltipArrowStyles) + } + }) + } + // debounce handler to prevent call twice when // mouse enter and focus events being triggered toggether const debouncedHandleShowTooltip = debounce(handleShowTooltip, 50) @@ -139,6 +175,7 @@ const Tooltip = ({ enabledEvents.push( { event: 'mouseenter', listener: debouncedHandleShowTooltip }, { event: 'mouseleave', listener: debouncedHandleHideTooltip }, + { event: 'mousemove', listener: (e) => handleMouseMove(e as MouseEvent) }, { event: 'focus', listener: debouncedHandleShowTooltip }, { event: 'blur', listener: debouncedHandleHideTooltip }, ) From 08775e9e400881116c9b40a97a9027c1189c129d Mon Sep 17 00:00:00 2001 From: Daniel Barion Date: Mon, 19 Dec 2022 12:42:42 -0300 Subject: [PATCH 02/19] feat[wip]: add type of tooltip as prop --- src/App.tsx | 7 ++++ src/components/Tooltip/Tooltip.tsx | 36 +++++++++++++------ src/components/Tooltip/TooltipTypes.d.ts | 3 ++ .../TooltipController/TooltipController.tsx | 2 ++ .../TooltipControllerTypes.d.ts | 2 ++ src/styles.module.css | 6 ++++ 6 files changed, 46 insertions(+), 10 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 810c4451c..9214740aa 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -112,6 +112,13 @@ function App() { +
+ ) } diff --git a/src/components/Tooltip/Tooltip.tsx b/src/components/Tooltip/Tooltip.tsx index 2b32c875a..0879fa462 100644 --- a/src/components/Tooltip/Tooltip.tsx +++ b/src/components/Tooltip/Tooltip.tsx @@ -24,6 +24,7 @@ const Tooltip = ({ delayHide = 0, noArrow, style: externalStyles, + type = 'fixed', // props handled by controller isHtmlContent = false, content, @@ -100,14 +101,6 @@ const Tooltip = ({ } } - const handleClickTooltipAnchor = () => { - if (setIsOpen) { - setIsOpen(!isOpen) - } else if (isOpen === undefined) { - setShow((currentValue) => !currentValue) - } - } - const handleMouseMove = (event?: MouseEvent) => { if (!event) { return @@ -126,6 +119,7 @@ const Tooltip = ({ } }, } as Element + computeTooltipPosition({ place, offset, @@ -144,6 +138,18 @@ const Tooltip = ({ }) } + const handleClickTooltipAnchor = (event: MouseEvent) => { + if (type === 'free') { + handleMouseMove(event) + } + + if (setIsOpen) { + setIsOpen(!isOpen) + } else if (!setIsOpen && isOpen === undefined) { + setShow((currentValue) => !currentValue) + } + } + // debounce handler to prevent call twice when // mouse enter and focus events being triggered toggether const debouncedHandleShowTooltip = debounce(handleShowTooltip, 50) @@ -175,10 +181,16 @@ const Tooltip = ({ enabledEvents.push( { event: 'mouseenter', listener: debouncedHandleShowTooltip }, { event: 'mouseleave', listener: debouncedHandleHideTooltip }, - { event: 'mousemove', listener: (e) => handleMouseMove(e as MouseEvent) }, { event: 'focus', listener: debouncedHandleShowTooltip }, { event: 'blur', listener: debouncedHandleHideTooltip }, ) + + if (type === 'float') { + enabledEvents.push({ + event: 'mousemove', + listener: (event) => handleMouseMove(event as MouseEvent), + }) + } } enabledEvents.forEach(({ event, listener }) => { @@ -197,6 +209,10 @@ const Tooltip = ({ }, [anchorRefs, anchorId, events, delayHide, delayShow]) useEffect(() => { + if (type === 'free') { + return () => null + } + let elementReference = activeAnchor.current if (anchorId) { // `anchorId` element takes precedence @@ -227,7 +243,7 @@ const Tooltip = ({ return () => { mounted = false } - }, [show, isOpen, anchorId, activeAnchor, content, place, offset, positionStrategy]) + }, [show, isOpen, anchorId, activeAnchor, content, place, offset, positionStrategy, type]) useEffect(() => { return () => { diff --git a/src/components/Tooltip/TooltipTypes.d.ts b/src/components/Tooltip/TooltipTypes.d.ts index f31c7822b..06f580ef0 100644 --- a/src/components/Tooltip/TooltipTypes.d.ts +++ b/src/components/Tooltip/TooltipTypes.d.ts @@ -12,6 +12,8 @@ export type EventsType = 'hover' | 'click' export type PositionStrategy = 'absolute' | 'fixed' +export type Type = 'fixed' | 'float' | 'free' + export type DataAttribute = | 'place' | 'content' @@ -39,6 +41,7 @@ export interface ITooltip { children?: ChildrenType events?: EventsType[] positionStrategy?: PositionStrategy + type?: Type delayShow?: number delayHide?: number noArrow?: boolean diff --git a/src/components/TooltipController/TooltipController.tsx b/src/components/TooltipController/TooltipController.tsx index 8ad0fc678..20dba6bd5 100644 --- a/src/components/TooltipController/TooltipController.tsx +++ b/src/components/TooltipController/TooltipController.tsx @@ -30,6 +30,7 @@ const TooltipController = ({ delayHide = 0, noArrow, style, + type, isOpen, setIsOpen, }: ITooltipController) => { @@ -179,6 +180,7 @@ const TooltipController = ({ delayHide: tooltipDelayHide, noArrow, style, + type, isOpen, setIsOpen, } diff --git a/src/components/TooltipController/TooltipControllerTypes.d.ts b/src/components/TooltipController/TooltipControllerTypes.d.ts index 82427fd4e..1b85859db 100644 --- a/src/components/TooltipController/TooltipControllerTypes.d.ts +++ b/src/components/TooltipController/TooltipControllerTypes.d.ts @@ -7,6 +7,7 @@ import type { ChildrenType, EventsType, PositionStrategy, + Type, } from 'components/Tooltip/TooltipTypes' export interface ITooltipController { @@ -27,6 +28,7 @@ export interface ITooltipController { delayHide?: number noArrow?: boolean style?: CSSProperties + type?: Type isOpen?: boolean setIsOpen?: (value: boolean) => void } diff --git a/src/styles.module.css b/src/styles.module.css index 8a3b67ae1..a0e5a814f 100644 --- a/src/styles.module.css +++ b/src/styles.module.css @@ -3,3 +3,9 @@ width: 100%; height: 100vh; } + +.freeAnchor { + width: 500px; + height: 500px; + background-color: #d3d3d3; +} From cc3f201113950351dd43a74e8bad0117e1519b30 Mon Sep 17 00:00:00 2001 From: Daniel Barion Date: Tue, 20 Dec 2022 12:13:10 -0300 Subject: [PATCH 03/19] refactor: update tooltip position feature implementation --- src/components/Tooltip/Tooltip.tsx | 37 +++++++------------ src/components/Tooltip/TooltipTypes.d.ts | 9 ++++- .../TooltipController/TooltipController.tsx | 4 +- .../TooltipControllerTypes.d.ts | 4 +- src/index.tsx | 2 + 5 files changed, 27 insertions(+), 29 deletions(-) diff --git a/src/components/Tooltip/Tooltip.tsx b/src/components/Tooltip/Tooltip.tsx index 0879fa462..8732c279b 100644 --- a/src/components/Tooltip/Tooltip.tsx +++ b/src/components/Tooltip/Tooltip.tsx @@ -24,7 +24,7 @@ const Tooltip = ({ delayHide = 0, noArrow, style: externalStyles, - type = 'fixed', + position, // props handled by controller isHtmlContent = false, content, @@ -101,21 +101,22 @@ const Tooltip = ({ } } - const handleMouseMove = (event?: MouseEvent) => { - if (!event) { + const handleTooltipPosition = () => { + if (!position?.x || !position?.y) { return } + const virtualElement = { getBoundingClientRect() { return { - x: event.clientX, - y: event.clientY, + x: position.x, + y: position.y, width: 0, height: 0, - top: event.clientY, - left: event.clientX, - right: event.clientX, - bottom: event.clientY, + top: position.y, + left: position.x, + right: position.x, + bottom: position.y, } }, } as Element @@ -138,11 +139,7 @@ const Tooltip = ({ }) } - const handleClickTooltipAnchor = (event: MouseEvent) => { - if (type === 'free') { - handleMouseMove(event) - } - + const handleClickTooltipAnchor = () => { if (setIsOpen) { setIsOpen(!isOpen) } else if (!setIsOpen && isOpen === undefined) { @@ -184,13 +181,6 @@ const Tooltip = ({ { event: 'focus', listener: debouncedHandleShowTooltip }, { event: 'blur', listener: debouncedHandleHideTooltip }, ) - - if (type === 'float') { - enabledEvents.push({ - event: 'mousemove', - listener: (event) => handleMouseMove(event as MouseEvent), - }) - } } enabledEvents.forEach(({ event, listener }) => { @@ -209,7 +199,8 @@ const Tooltip = ({ }, [anchorRefs, anchorId, events, delayHide, delayShow]) useEffect(() => { - if (type === 'free') { + if (position?.x && position?.y) { + handleTooltipPosition() return () => null } @@ -243,7 +234,7 @@ const Tooltip = ({ return () => { mounted = false } - }, [show, isOpen, anchorId, activeAnchor, content, place, offset, positionStrategy, type]) + }, [show, isOpen, anchorId, activeAnchor, content, place, offset, positionStrategy, position]) useEffect(() => { return () => { diff --git a/src/components/Tooltip/TooltipTypes.d.ts b/src/components/Tooltip/TooltipTypes.d.ts index 06f580ef0..a634acafa 100644 --- a/src/components/Tooltip/TooltipTypes.d.ts +++ b/src/components/Tooltip/TooltipTypes.d.ts @@ -1,4 +1,4 @@ -import type { ElementType, ReactNode, Element, CSSProperties } from 'react' +import type { ElementType, ReactNode, CSSProperties } from 'react' export type PlacesType = 'top' | 'right' | 'bottom' | 'left' @@ -26,6 +26,11 @@ export type DataAttribute = | 'delay-show' | 'delay-hide' +export interface IPosition { + x?: number + y?: number +} + export interface ITooltip { className?: string classNameArrow?: string @@ -41,11 +46,11 @@ export interface ITooltip { children?: ChildrenType events?: EventsType[] positionStrategy?: PositionStrategy - type?: Type delayShow?: number delayHide?: number noArrow?: boolean style?: CSSProperties + position?: IPosition isOpen?: boolean setIsOpen?: (value: boolean) => void } diff --git a/src/components/TooltipController/TooltipController.tsx b/src/components/TooltipController/TooltipController.tsx index 20dba6bd5..bb35e8f7d 100644 --- a/src/components/TooltipController/TooltipController.tsx +++ b/src/components/TooltipController/TooltipController.tsx @@ -30,7 +30,7 @@ const TooltipController = ({ delayHide = 0, noArrow, style, - type, + position, isOpen, setIsOpen, }: ITooltipController) => { @@ -180,7 +180,7 @@ const TooltipController = ({ delayHide: tooltipDelayHide, noArrow, style, - type, + position, isOpen, setIsOpen, } diff --git a/src/components/TooltipController/TooltipControllerTypes.d.ts b/src/components/TooltipController/TooltipControllerTypes.d.ts index 1b85859db..1055e29f8 100644 --- a/src/components/TooltipController/TooltipControllerTypes.d.ts +++ b/src/components/TooltipController/TooltipControllerTypes.d.ts @@ -7,7 +7,7 @@ import type { ChildrenType, EventsType, PositionStrategy, - Type, + IPosition, } from 'components/Tooltip/TooltipTypes' export interface ITooltipController { @@ -28,7 +28,7 @@ export interface ITooltipController { delayHide?: number noArrow?: boolean style?: CSSProperties - type?: Type + position?: IPosition isOpen?: boolean setIsOpen?: (value: boolean) => void } diff --git a/src/index.tsx b/src/index.tsx index 9fcde48ca..32b260291 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -7,6 +7,7 @@ import type { PositionStrategy, VariantType, WrapperType, + IPosition, } from './components/Tooltip/TooltipTypes' import type { ITooltipController } from './components/TooltipController/TooltipControllerTypes' import type { ITooltipWrapper } from './components/TooltipProvider/TooltipProviderTypes' @@ -23,4 +24,5 @@ export type { WrapperType, ITooltipController as ITooltip, ITooltipWrapper, + IPosition, } From eed1200620ea56d7464184981c6bae5d045027dd Mon Sep 17 00:00:00 2001 From: Daniel Barion Date: Tue, 20 Dec 2022 12:13:35 -0300 Subject: [PATCH 04/19] test: add tests to the new feature --- src/test/__snapshots__/index.spec.js.snap | 21 +++++++++++++++++++++ src/test/index.spec.js | 8 ++++++++ 2 files changed, 29 insertions(+) diff --git a/src/test/__snapshots__/index.spec.js.snap b/src/test/__snapshots__/index.spec.js.snap index 6b000732c..555b5e87f 100644 --- a/src/test/__snapshots__/index.spec.js.snap +++ b/src/test/__snapshots__/index.spec.js.snap @@ -69,6 +69,27 @@ exports[`tooltip props tooltip component - html 1`] = ` ] `; +exports[`tooltip props tooltip component - position props 1`] = ` +[ + + Lorem Ipsum + , +
+ Hello World! +
+
, +] +`; + exports[`tooltip props tooltip component - without anchorId 1`] = ` [ diff --git a/src/test/index.spec.js b/src/test/index.spec.js index 9a466cfdf..1e50ba56a 100644 --- a/src/test/index.spec.js +++ b/src/test/index.spec.js @@ -54,6 +54,14 @@ describe('tooltip props', () => { const tree = component.toJSON() expect(tree).toMatchSnapshot() }) + + test('tooltip component - position props', () => { + const component = renderer.create( + , + ) + const tree = component.toJSON() + expect(tree).toMatchSnapshot() + }) }) describe('compute positions', () => { From f0284269019872518d80fc3cfa69771db237729b Mon Sep 17 00:00:00 2001 From: Daniel Barion Date: Tue, 20 Dec 2022 12:14:00 -0300 Subject: [PATCH 05/19] chore: add example into app.tsx about position feature --- src/App.tsx | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 2 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 9214740aa..aeff4c16b 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,5 +1,8 @@ +/* eslint-disable jsx-a11y/no-static-element-interactions */ +/* eslint-disable jsx-a11y/click-events-have-key-events */ import { TooltipController as Tooltip } from 'components/TooltipController' import { TooltipProvider, TooltipWrapper } from 'components/TooltipProvider' +import { IPosition } from 'components/Tooltip/TooltipTypes.d' import { useState } from 'react' import styles from './styles.module.css' @@ -39,6 +42,30 @@ function WithProviderMultiple() { function App() { const [anchorId, setAnchorId] = useState('button') const [isDarkOpen, setIsDarkOpen] = useState(false) + const [position, setPosition] = useState({}) + const [tooltipEffect, setTooltipEffect] = useState('float') + + const handlePositionClick = (event: MouseEvent) => { + if (tooltipEffect === 'float') { + return + } + + const x = event.clientX + const y = event.clientY + + setPosition({ x, y }) + } + + const handleMouseMove = (event: MouseEvent) => { + if (tooltipEffect !== 'float') { + return + } + + const x = event.clientX + const y = event.clientY + + setPosition({ x, y }) + } return (
@@ -112,12 +139,33 @@ function App() { -
+ + +
{ + handlePositionClick(event as MouseEvent) + }} + onMouseMove={(event: any) => { + handleMouseMove(event as MouseEvent) + }} + />
) From 8815eed61e85bd9a5c60af57ae5e8b3905f0a1ac Mon Sep 17 00:00:00 2001 From: Daniel Barion Date: Tue, 20 Dec 2022 12:43:44 -0300 Subject: [PATCH 06/19] docs: update project readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a3b25ec63..d8ff75d15 100755 --- a/README.md +++ b/README.md @@ -112,7 +112,7 @@ import ReactDOMServer from 'react-dom/server'; [gabrieljablonski](https://github.com/gabrieljablonski) Maintainer. -[aronhelser](https://github.com/aronhelser) Passive maintainer - accepting PRs and doing minor testing, but not fixing issues or doing active development. +[aronhelser](https://github.com/aronhelser) (inactive). [alexgurr](https://github.com/alexgurr) (inactive). From dc4d4044075b1c120e64a900b56a0ce329fb62c0 Mon Sep 17 00:00:00 2001 From: gabrieljablonski Date: Tue, 20 Dec 2022 17:10:47 -0300 Subject: [PATCH 07/19] fix: pad arrow position to avoid overflow --- src/utils/compute-positions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/compute-positions.ts b/src/utils/compute-positions.ts index 34f6fc905..435ad45a9 100644 --- a/src/utils/compute-positions.ts +++ b/src/utils/compute-positions.ts @@ -23,7 +23,7 @@ export const computeTooltipPosition = async ({ const middleware = [offset(Number(offsetValue)), flip(), shift({ padding: 5 })] if (tooltipArrowReference) { - middleware.push(arrow({ element: tooltipArrowReference as HTMLElement })) + middleware.push(arrow({ element: tooltipArrowReference as HTMLElement, padding: 5 })) return computePosition(elementReference as HTMLElement, tooltipReference as HTMLElement, { placement: place, strategy, From c2c222a7a4122334b334af5a9b112244314c4a4c Mon Sep 17 00:00:00 2001 From: gabrieljablonski Date: Tue, 20 Dec 2022 17:13:03 -0300 Subject: [PATCH 08/19] refactor: avoid eslint `no-empty-function` warning --- src/components/Tooltip/Tooltip.tsx | 3 +-- src/components/TooltipController/TooltipController.tsx | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/components/Tooltip/Tooltip.tsx b/src/components/Tooltip/Tooltip.tsx index 8732c279b..89ba8b0b2 100644 --- a/src/components/Tooltip/Tooltip.tsx +++ b/src/components/Tooltip/Tooltip.tsx @@ -164,8 +164,7 @@ const Tooltip = ({ } if (!elementRefs.size) { - // eslint-disable-next-line @typescript-eslint/no-empty-function - return () => {} + return () => null } const enabledEvents: { event: string; listener: (event?: Event) => void }[] = [] diff --git a/src/components/TooltipController/TooltipController.tsx b/src/components/TooltipController/TooltipController.tsx index bb35e8f7d..97f42b33d 100644 --- a/src/components/TooltipController/TooltipController.tsx +++ b/src/components/TooltipController/TooltipController.tsx @@ -122,8 +122,7 @@ const TooltipController = ({ } if (!elementRefs.size) { - // eslint-disable-next-line @typescript-eslint/no-empty-function - return () => {} + return () => null } const observerCallback: MutationCallback = (mutationList) => { From 9f5311a1e89ef879265a605e9845b715e787f11a Mon Sep 17 00:00:00 2001 From: gabrieljablonski Date: Tue, 20 Dec 2022 17:23:39 -0300 Subject: [PATCH 09/19] chore: remove manual `float` example from dev demo --- src/App.tsx | 54 ++++++++++--------------------------------- src/styles.module.css | 7 ++++-- 2 files changed, 17 insertions(+), 44 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index aeff4c16b..d5950bfa8 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -8,7 +8,7 @@ import styles from './styles.module.css' function WithProviderMinimal() { return ( -
+

@@ -24,7 +24,7 @@ function WithProviderMinimal() { function WithProviderMultiple() { return ( -

+

@@ -43,27 +43,10 @@ function App() { const [anchorId, setAnchorId] = useState('button') const [isDarkOpen, setIsDarkOpen] = useState(false) const [position, setPosition] = useState({}) - const [tooltipEffect, setTooltipEffect] = useState('float') - - const handlePositionClick = (event: MouseEvent) => { - if (tooltipEffect === 'float') { - return - } + const handlePositionClick: React.MouseEventHandler = (event) => { const x = event.clientX const y = event.clientY - - setPosition({ x, y }) - } - - const handleMouseMove = (event: MouseEvent) => { - if (tooltipEffect !== 'float') { - return - } - - const x = event.clientX - const y = event.clientY - setPosition({ x, y }) } @@ -139,31 +122,18 @@ function App() { - -

{ - handlePositionClick(event as MouseEvent) - }} - onMouseMove={(event: any) => { - handleMouseMove(event as MouseEvent) + id="onClickAnchor" + className={styles['on-click-anchor']} + onClick={(event) => { + handlePositionClick(event) }} - /> + > + Click me! +
diff --git a/src/styles.module.css b/src/styles.module.css index a0e5a814f..62dcf0dba 100644 --- a/src/styles.module.css +++ b/src/styles.module.css @@ -4,8 +4,11 @@ height: 100vh; } -.freeAnchor { +.on-click-anchor { width: 500px; - height: 500px; + height: 300px; background-color: #d3d3d3; + display: flex; + align-items: center; + justify-content: center; } From 88465f890813fcf438c91de6eb91c993a30677f0 Mon Sep 17 00:00:00 2001 From: gabrieljablonski Date: Tue, 20 Dec 2022 17:25:34 -0300 Subject: [PATCH 10/19] feat: close `click` tooltip on click outside --- src/components/Tooltip/Tooltip.tsx | 13 +++++++++++-- src/components/Tooltip/TooltipTypes.d.ts | 2 -- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/components/Tooltip/Tooltip.tsx b/src/components/Tooltip/Tooltip.tsx index 89ba8b0b2..c32bd3515 100644 --- a/src/components/Tooltip/Tooltip.tsx +++ b/src/components/Tooltip/Tooltip.tsx @@ -143,10 +143,17 @@ const Tooltip = ({ if (setIsOpen) { setIsOpen(!isOpen) } else if (!setIsOpen && isOpen === undefined) { - setShow((currentValue) => !currentValue) + setShow(true) } } + const handleClickOutsideAnchor = (e: MouseEvent) => { + if (e.target === activeAnchor.current) { + return + } + setShow(false) + } + // debounce handler to prevent call twice when // mouse enter and focus events being triggered toggether const debouncedHandleShowTooltip = debounce(handleShowTooltip, 50) @@ -170,6 +177,7 @@ const Tooltip = ({ const enabledEvents: { event: string; listener: (event?: Event) => void }[] = [] if (events.find((event: string) => event === 'click')) { + window.addEventListener('click', handleClickOutsideAnchor) enabledEvents.push({ event: 'click', listener: handleClickTooltipAnchor }) } @@ -189,13 +197,14 @@ const Tooltip = ({ }) return () => { + window.removeEventListener('click', handleClickOutsideAnchor) enabledEvents.forEach(({ event, listener }) => { elementRefs.forEach((ref) => { ref.current?.removeEventListener(event, listener) }) }) } - }, [anchorRefs, anchorId, events, delayHide, delayShow]) + }, [anchorRefs, activeAnchor, anchorId, events, delayHide, delayShow]) useEffect(() => { if (position?.x && position?.y) { diff --git a/src/components/Tooltip/TooltipTypes.d.ts b/src/components/Tooltip/TooltipTypes.d.ts index a634acafa..43e57d54f 100644 --- a/src/components/Tooltip/TooltipTypes.d.ts +++ b/src/components/Tooltip/TooltipTypes.d.ts @@ -12,8 +12,6 @@ export type EventsType = 'hover' | 'click' export type PositionStrategy = 'absolute' | 'fixed' -export type Type = 'fixed' | 'float' | 'free' - export type DataAttribute = | 'place' | 'content' From fae87967b42052e51758c1aefa7e419699862768 Mon Sep 17 00:00:00 2001 From: gabrieljablonski Date: Tue, 20 Dec 2022 17:28:04 -0300 Subject: [PATCH 11/19] feat: use `delayHide` on `click` tooltip --- src/components/Tooltip/Tooltip.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/components/Tooltip/Tooltip.tsx b/src/components/Tooltip/Tooltip.tsx index c32bd3515..9f2434c3d 100644 --- a/src/components/Tooltip/Tooltip.tsx +++ b/src/components/Tooltip/Tooltip.tsx @@ -144,6 +144,9 @@ const Tooltip = ({ setIsOpen(!isOpen) } else if (!setIsOpen && isOpen === undefined) { setShow(true) + if (delayHide) { + handleHideTooltipDelayed() + } } } From 7859ae31fdfd641ce7e3ca757535deba10feb0f4 Mon Sep 17 00:00:00 2001 From: gabrieljablonski Date: Tue, 20 Dec 2022 17:41:35 -0300 Subject: [PATCH 12/19] refactor: `position` must have `x` and `y` --- src/App.tsx | 2 +- src/components/Tooltip/Tooltip.tsx | 7 ++++--- src/components/Tooltip/TooltipTypes.d.ts | 4 ++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index d5950bfa8..2e30c1115 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -42,7 +42,7 @@ function WithProviderMultiple() { function App() { const [anchorId, setAnchorId] = useState('button') const [isDarkOpen, setIsDarkOpen] = useState(false) - const [position, setPosition] = useState({}) + const [position, setPosition] = useState({ x: 0, y: 0 }) const handlePositionClick: React.MouseEventHandler = (event) => { const x = event.clientX diff --git a/src/components/Tooltip/Tooltip.tsx b/src/components/Tooltip/Tooltip.tsx index 9f2434c3d..ef0556329 100644 --- a/src/components/Tooltip/Tooltip.tsx +++ b/src/components/Tooltip/Tooltip.tsx @@ -102,7 +102,7 @@ const Tooltip = ({ } const handleTooltipPosition = () => { - if (!position?.x || !position?.y) { + if (!position) { return } @@ -120,7 +120,7 @@ const Tooltip = ({ } }, } as Element - + setCalculatingPosition(true) computeTooltipPosition({ place, offset, @@ -210,7 +210,8 @@ const Tooltip = ({ }, [anchorRefs, activeAnchor, anchorId, events, delayHide, delayShow]) useEffect(() => { - if (position?.x && position?.y) { + if (position) { + // if `position` is set, override regular positioning handleTooltipPosition() return () => null } diff --git a/src/components/Tooltip/TooltipTypes.d.ts b/src/components/Tooltip/TooltipTypes.d.ts index 43e57d54f..77baf5140 100644 --- a/src/components/Tooltip/TooltipTypes.d.ts +++ b/src/components/Tooltip/TooltipTypes.d.ts @@ -25,8 +25,8 @@ export type DataAttribute = | 'delay-hide' export interface IPosition { - x?: number - y?: number + x: number + y: number } export interface ITooltip { From 49fdb263726622d6d6b836a5b3d3a075550488ff Mon Sep 17 00:00:00 2001 From: gabrieljablonski Date: Tue, 20 Dec 2022 18:13:14 -0300 Subject: [PATCH 13/19] feat: add `float` mode (follow mouse) --- src/App.tsx | 53 +++++++++++----- src/components/Tooltip/Tooltip.tsx | 60 ++++++++++++++----- src/components/Tooltip/TooltipTypes.d.ts | 2 + .../TooltipController/TooltipController.tsx | 6 ++ .../TooltipControllerTypes.d.ts | 2 + src/styles.module.css | 2 +- 6 files changed, 96 insertions(+), 29 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 2e30c1115..0c382aa32 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -43,6 +43,7 @@ function App() { const [anchorId, setAnchorId] = useState('button') const [isDarkOpen, setIsDarkOpen] = useState(false) const [position, setPosition] = useState({ x: 0, y: 0 }) + const [toggle, setToggle] = useState(false) const handlePositionClick: React.MouseEventHandler = (event) => { const x = event.clientX @@ -122,21 +123,45 @@ function App() { -
{ - handlePositionClick(event) - }} - > - Click me! +
+
+
{ + setToggle((t) => !t) + }} + > + Hover me! +
+ +
+
+
{ + handlePositionClick(event) + }} + > + Click me! +
+ +
- ) } diff --git a/src/components/Tooltip/Tooltip.tsx b/src/components/Tooltip/Tooltip.tsx index ef0556329..55b577f7e 100644 --- a/src/components/Tooltip/Tooltip.tsx +++ b/src/components/Tooltip/Tooltip.tsx @@ -5,7 +5,7 @@ import { TooltipContent } from 'components/TooltipContent' import { useTooltip } from 'components/TooltipProvider' import { computeTooltipPosition } from '../../utils/compute-positions' import styles from './styles.module.css' -import type { ITooltip } from './TooltipTypes' +import type { IPosition, ITooltip } from './TooltipTypes' const Tooltip = ({ // props @@ -22,6 +22,7 @@ const Tooltip = ({ children = null, delayShow = 0, delayHide = 0, + float = false, noArrow, style: externalStyles, position, @@ -39,6 +40,7 @@ const Tooltip = ({ const [inlineArrowStyles, setInlineArrowStyles] = useState({}) const [show, setShow] = useState(false) const [calculatingPosition, setCalculatingPosition] = useState(false) + const [lastFloatPosition, setLastFloatPosition] = useState(null) const { anchorRefs, setActiveAnchor: setProviderActiveAnchor } = useTooltip()(id) const [activeAnchor, setActiveAnchor] = useState>({ current: null }) @@ -101,22 +103,18 @@ const Tooltip = ({ } } - const handleTooltipPosition = () => { - if (!position) { - return - } - + const handleTooltipPosition = ({ x, y }: IPosition) => { const virtualElement = { getBoundingClientRect() { return { - x: position.x, - y: position.y, + x, + y, width: 0, height: 0, - top: position.y, - left: position.x, - right: position.x, - bottom: position.y, + top: y, + left: x, + right: x, + bottom: y, } }, } as Element @@ -139,6 +137,19 @@ const Tooltip = ({ }) } + const handleMouseMove = (event?: Event) => { + if (!event) { + return + } + const e = event as MouseEvent + const p = { + x: e.clientX, + y: e.clientY, + } + handleTooltipPosition(p) + setLastFloatPosition(p) + } + const handleClickTooltipAnchor = () => { if (setIsOpen) { setIsOpen(!isOpen) @@ -191,6 +202,12 @@ const Tooltip = ({ { event: 'focus', listener: debouncedHandleShowTooltip }, { event: 'blur', listener: debouncedHandleHideTooltip }, ) + if (float) { + enabledEvents.push({ + event: 'mousemove', + listener: handleMouseMove, + }) + } } enabledEvents.forEach(({ event, listener }) => { @@ -211,8 +228,23 @@ const Tooltip = ({ useEffect(() => { if (position) { - // if `position` is set, override regular positioning - handleTooltipPosition() + // if `position` is set, override regular and `float` positioning + handleTooltipPosition(position) + return () => null + } + + if (float) { + if (lastFloatPosition) { + /* + Without this, changes to `content`, `place`, `offset`, ..., will only + trigger a position calculation after a `mousemove` event. + + To see why this matters, comment this line, run `yarn dev` and click the + "Hover me!" anchor. + */ + handleTooltipPosition(lastFloatPosition) + } + // if `float` is set, override regular positioning return () => null } diff --git a/src/components/Tooltip/TooltipTypes.d.ts b/src/components/Tooltip/TooltipTypes.d.ts index 77baf5140..924896ce1 100644 --- a/src/components/Tooltip/TooltipTypes.d.ts +++ b/src/components/Tooltip/TooltipTypes.d.ts @@ -23,6 +23,7 @@ export type DataAttribute = | 'position-strategy' | 'delay-show' | 'delay-hide' + | 'float' export interface IPosition { x: number @@ -46,6 +47,7 @@ export interface ITooltip { positionStrategy?: PositionStrategy delayShow?: number delayHide?: number + float?: boolean noArrow?: boolean style?: CSSProperties position?: IPosition diff --git a/src/components/TooltipController/TooltipController.tsx b/src/components/TooltipController/TooltipController.tsx index 97f42b33d..645499dac 100644 --- a/src/components/TooltipController/TooltipController.tsx +++ b/src/components/TooltipController/TooltipController.tsx @@ -28,6 +28,7 @@ const TooltipController = ({ positionStrategy = 'absolute', delayShow = 0, delayHide = 0, + float = false, noArrow, style, position, @@ -40,6 +41,7 @@ const TooltipController = ({ const [tooltipOffset, setTooltipOffset] = useState(offset) const [tooltipDelayShow, setTooltipDelayShow] = useState(delayShow) const [tooltipDelayHide, setTooltipDelayHide] = useState(delayHide) + const [tooltipFloat, setTooltipFloat] = useState(float) const [tooltipWrapper, setTooltipWrapper] = useState(wrapper) const [tooltipEvents, setTooltipEvents] = useState(events) const [tooltipPositionStrategy, setTooltipPositionStrategy] = useState(positionStrategy) @@ -95,6 +97,9 @@ const TooltipController = ({ 'delay-hide': (value) => { setTooltipDelayHide(value === null ? delayHide : Number(value)) }, + float: (value) => { + setTooltipFloat(value === null ? float : Boolean(value)) + }, } // reset unset data attributes to default values // without this, data attributes from the last active anchor will still be used @@ -177,6 +182,7 @@ const TooltipController = ({ positionStrategy: tooltipPositionStrategy, delayShow: tooltipDelayShow, delayHide: tooltipDelayHide, + float: tooltipFloat, noArrow, style, position, diff --git a/src/components/TooltipController/TooltipControllerTypes.d.ts b/src/components/TooltipController/TooltipControllerTypes.d.ts index 1055e29f8..5901b2054 100644 --- a/src/components/TooltipController/TooltipControllerTypes.d.ts +++ b/src/components/TooltipController/TooltipControllerTypes.d.ts @@ -26,6 +26,7 @@ export interface ITooltipController { positionStrategy?: PositionStrategy delayShow?: number delayHide?: number + float?: boolean noArrow?: boolean style?: CSSProperties position?: IPosition @@ -45,5 +46,6 @@ declare module 'react' { 'data-tooltip-position-strategy'?: PositionStrategy 'data-tooltip-delay-show'?: number 'data-tooltip-delay-hide'?: number + 'data-tooltip-float'?: boolean } } diff --git a/src/styles.module.css b/src/styles.module.css index 62dcf0dba..e2b323224 100644 --- a/src/styles.module.css +++ b/src/styles.module.css @@ -4,7 +4,7 @@ height: 100vh; } -.on-click-anchor { +.big-anchor { width: 500px; height: 300px; background-color: #d3d3d3; From 7f290e47e3442dfea076a86088dd4f7dfa86b1eb Mon Sep 17 00:00:00 2001 From: gabrieljablonski Date: Tue, 20 Dec 2022 18:21:29 -0300 Subject: [PATCH 14/19] test: account for added arrow padding --- src/test/index.spec.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/index.spec.js b/src/test/index.spec.js index 1e50ba56a..db87b46eb 100644 --- a/src/test/index.spec.js +++ b/src/test/index.spec.js @@ -117,12 +117,12 @@ describe('compute positions', () => { expect(value).toEqual({ tooltipArrowStyles: { bottom: '-4px', - left: '0px', + left: '5px', right: '', top: '', }, tooltipStyles: { - left: '5px', + left: '-5px', top: '-10px', }, }) From 1e9ddd0fae705262746339ad374a816390e9e5f6 Mon Sep 17 00:00:00 2001 From: gabrieljablonski Date: Tue, 20 Dec 2022 19:00:12 -0300 Subject: [PATCH 15/19] refactor: var naming --- src/components/Tooltip/Tooltip.tsx | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/components/Tooltip/Tooltip.tsx b/src/components/Tooltip/Tooltip.tsx index 55b577f7e..e05857c29 100644 --- a/src/components/Tooltip/Tooltip.tsx +++ b/src/components/Tooltip/Tooltip.tsx @@ -141,13 +141,13 @@ const Tooltip = ({ if (!event) { return } - const e = event as MouseEvent - const p = { - x: e.clientX, - y: e.clientY, + const mouseEvent = event as MouseEvent + const mousePosition = { + x: mouseEvent.clientX, + y: mouseEvent.clientY, } - handleTooltipPosition(p) - setLastFloatPosition(p) + handleTooltipPosition(mousePosition) + setLastFloatPosition(mousePosition) } const handleClickTooltipAnchor = () => { @@ -161,8 +161,8 @@ const Tooltip = ({ } } - const handleClickOutsideAnchor = (e: MouseEvent) => { - if (e.target === activeAnchor.current) { + const handleClickOutsideAnchor = (event: MouseEvent) => { + if (event.target === activeAnchor.current) { return } setShow(false) From e8058a6cb294f266a3634a7dbad87148cb08bbfc Mon Sep 17 00:00:00 2001 From: gabrieljablonski Date: Tue, 20 Dec 2022 19:14:03 -0300 Subject: [PATCH 16/19] docs: `position`/`float` props and general improvements --- docs/docs/options.mdx | 73 +++++++-------- docs/docs/upgrade-guide/changelog-v4-v5.md | 100 +++++++++++---------- 2 files changed, 89 insertions(+), 84 deletions(-) diff --git a/docs/docs/options.mdx b/docs/docs/options.mdx index eed2a59bc..8947fd82b 100644 --- a/docs/docs/options.mdx +++ b/docs/docs/options.mdx @@ -49,27 +49,29 @@ import 'react-tooltip/dist/react-tooltip.css' #### Available props -| name | type | required | default | values | description | -| ---------------- | -------------------- | -------- | ------------------------- | ------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| className | string | false | | | class name to customize tooltip element | -| classNameArrow | string | false | | | class name to customize tooltip arrow element | -| content | string | false | | | content to de displayed in tooltip (`content` prop is priorized over `html`) | -| html | string | false | | | html content to de displayed in tooltip | -| place | string | false | `top` | `top` `right` `bottom` `left` | place related to anchor element where the tooltip will be rendered if possible | -| offset | number | false | `10` | any `number` | space between the tooltip element and anchor element (arrow not included in calc) | -| id | string | false | React `useId` | any `string` | option to set a specific id into the tooltip element, the default value is handled by React `useId` introduced on React 18 | -| anchorId | string | false | `undefined` | any `string` | id reference from the element that the tooltip will be positioned around | -| variant | string | false | `dark` | `dark` `light` `success` `warning` `error` `info` | change the colors of tooltip with pre-defined values | -| wrapper | valid element | false | `div` | `ElementType` `div` `span` | element wrapper of tooltip container, can be `div`, `span` or any valid Element | -| children | valid React children | false | `undefined` | valid React children | content can be pass through props, data-attributes or as children, children will be priorized over other options | -| events | array | false | `hover` | `hover` `click` | pre-defined events to handle show or hide tooltip | -| positionStrategy | string | false | `absolute` | `absolute` `fixed` | the position strategy used for the tooltip. set to `fixed` if you run into issues with `overflow: hidden` on the tooltip parent container | -| delayShow | number | false | | any `number` | tooltip show will be delayed in miliseconds by the amount of value | -| delayHide | number | false | | any `number` | tooltip hide will be delayed in miliseconds by the amount of value | -| noArrow | boolean | false | `false` | `true` `false` | tooltip arrow will not be shown | -| style | CSSProperties | false | | any React inline style | add styles directly to the component by `style` attribute | -| isOpen | boolen | false | handled by internal state | `true` `false` | the tooltip can be controlled or uncontrolled, this attribute can be used to handle show and hide tooltip outside tooltip (can be used **without** `setIsOpen`) | -| setIsOpen | function | false | | | the tooltip can be controlled or uncontrolled, this attribute can be used to handle show and hide tooltip outside tooltip | +| name | type | required | default | values | description | +| ---------------- | -------------------------- | -------- | ------------------------- | ------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| className | string | false | | | class name to customize tooltip element | +| classNameArrow | string | false | | | class name to customize tooltip arrow element | +| content | string | false | | | content to de displayed in tooltip (`content` prop is priorized over `html`) | +| html | string | false | | | html content to de displayed in tooltip | +| place | string | false | `top` | `top` `right` `bottom` `left` | place related to anchor element where the tooltip will be rendered if possible | +| offset | number | false | `10` | any `number` | space between the tooltip element and anchor element (arrow not included in calc) | +| id | string | false | React `useId` | any `string` | option to set a specific id into the tooltip element, the default value is handled by React `useId` introduced on React 18 | +| anchorId | string | false | `undefined` | any `string` | id reference from the element that the tooltip will be positioned around | +| variant | string | false | `dark` | `dark` `light` `success` `warning` `error` `info` | change the colors of tooltip with pre-defined values | +| wrapper | valid element | false | `div` | `ElementType` `div` `span` | element wrapper of tooltip container, can be `div`, `span` or any valid Element | +| children | valid React children | false | `undefined` | valid React children | content can be pass through props, data-attributes or as children, children will be priorized over other options | +| events | array | false | `hover` | `hover` `click` | pre-defined events to handle show or hide tooltip | +| positionStrategy | string | false | `absolute` | `absolute` `fixed` | the position strategy used for the tooltip. set to `fixed` if you run into issues with `overflow: hidden` on the tooltip parent container | +| delayShow | number | false | | any `number` | tooltip show will be delayed in miliseconds by the amount of value | +| delayHide | number | false | | any `number` | tooltip hide will be delayed in miliseconds by the amount of value | +| float | boolean | false | `false` | `true` `false` | tooltip will follow the mouse position when it moves inside the anchor element (same as V4's `effect="float"`) | +| noArrow | boolean | false | `false` | `true` `false` | tooltip arrow will not be shown | +| style | CSSProperties | false | | any React inline style | add styles directly to the component by `style` attribute | +| position | `{ x: number; y: number }` | false | | any `number` value for both `x` and `y` | override the tooltip position on the viewport | +| isOpen | boolen | false | handled by internal state | `true` `false` | the tooltip can be controlled or uncontrolled, this attribute can be used to handle show and hide tooltip outside tooltip (can be used **without** `setIsOpen`) | +| setIsOpen | function | false | | | the tooltip can be controlled or uncontrolled, this attribute can be used to handle show and hide tooltip outside tooltip | ### Data attributes @@ -77,30 +79,31 @@ import 'react-tooltip/dist/react-tooltip.css' import { Tooltip } from 'react-tooltip'; import 'react-tooltip/dist/react-tooltip.css'; - ◕‿‿◕ + ◕‿‿◕ ``` - + ◕‿‿◕ #### Available attributes -| name | type | required | default | values | description | -| ------------------------------ | ------ | -------- | ---------- | ------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | -| data-tooltip-content | string | false | | | content to de displayed in tooltip (`content` prop is priorized over `html`) | -| data-tooltip-html | string | false | | | html content to de displayed in tooltip | -| data-tooltip-place | string | false | `top` | `top` `right` `bottom` `left` | place related to anchor element where the tooltip will be rendered if possible | -| data-tooltip-offset | number | false | `10` | any `number` | space between the tooltip element and anchor element (arrow not included in calc) | -| data-tooltip-variant | string | false | `dark` | `dark` `light` `success` `warning` `error` `info` | change the colors of tooltip with pre-defined values | -| data-tooltip-wrapper | string | false | `div` | `div` `span` | element wrapper of tooltip container, can be `div`, `span` or any valid Element | -| data-tooltip-events | string | false | `hover` | `hover click` `hover` `click` | pre-defined events to handle show or hide tooltip | -| data-tooltip-position-strategy | string | false | `absolute` | `absolute` `fixed` | the position strategy used for the tooltip. set to `fixed` if you run into issues with `overflow: hidden` on the tooltip parent container | -| data-tooltip-show | number | false | | any `number` | tooltip show will be delayed in miliseconds by the amount of value | -| data-tooltip-hide | number | false | | any `number` | tooltip hide will be delayed in miliseconds by the amount of value | +| name | type | required | default | values | description | +| ------------------------------ | ------- | -------- | ---------- | ------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | +| data-tooltip-content | string | false | | | content to de displayed in tooltip (`content` prop is priorized over `html`) | +| data-tooltip-html | string | false | | | html content to de displayed in tooltip | +| data-tooltip-place | string | false | `top` | `top` `right` `bottom` `left` | place related to anchor element where the tooltip will be rendered if possible | +| data-tooltip-offset | number | false | `10` | any `number` | space between the tooltip element and anchor element (arrow not included in calc) | +| data-tooltip-variant | string | false | `dark` | `dark` `light` `success` `warning` `error` `info` | change the colors of tooltip with pre-defined values | +| data-tooltip-wrapper | string | false | `div` | `div` `span` | element wrapper of tooltip container, can be `div`, `span` or any valid Element | +| data-tooltip-events | string | false | `hover` | `hover click` `hover` `click` | pre-defined events to handle show or hide tooltip | +| data-tooltip-position-strategy | string | false | `absolute` | `absolute` `fixed` | the position strategy used for the tooltip. set to `fixed` if you run into issues with `overflow: hidden` on the tooltip parent container | +| data-tooltip-show | number | false | | any `number` | tooltip show will be delayed in miliseconds by the amount of value | +| data-tooltip-hide | number | false | | any `number` | tooltip hide will be delayed in miliseconds by the amount of value | +| data-tooltip-float | boolean | false | `false` | `true` `false` | tooltip will follow the mouse position when it moves inside the anchor element (same as V4's `effect="float"`) | #### Observations diff --git a/docs/docs/upgrade-guide/changelog-v4-v5.md b/docs/docs/upgrade-guide/changelog-v4-v5.md index c10f8d979..ba82b4fdc 100644 --- a/docs/docs/upgrade-guide/changelog-v4-v5.md +++ b/docs/docs/upgrade-guide/changelog-v4-v5.md @@ -8,74 +8,76 @@ If you are using V4 and want to use V5, please read this doc. ## From V4 to V5 -V4 was a great react tooltip component but was built a few years ago, he was built with react class components and it's hard to maintain and to the community give contributions, so, with this in mind, we build a new version of react tooltip using [float-ui](https://floating-ui.com/) behind the scenes. This gives a great improvement in performance and in a new and easier code to let the community contribute to the project. +V4 was a great react tooltip component but was built a few years ago, he was built with react class components and it's hard to maintain and to the community give contributions, so, with this in mind, we build a new version of react tooltip using [floating-ui](https://floating-ui.com/) behind the scenes. This gives a great improvement in performance and in a new and easier code to let the community contribute to the project. ## Improvements - Dropped package dependency `uuid` - - Using React `useId` - [Docs](https://reactjs.org/docs/hooks-reference.html#useid) -- - - Unfortunately `useId` was introduced only into React v18, so, that will be the minimum necessary version of React to V5 +- - - Unfortunately `useId` was introduced only into React v18, so that is the minimum required React version to use V5 - Dropped package dependency `prop-types` - V5 is written in TypeScript - V5 has minified and unminified files available to be used as you want -## Break Changes +## Breaking Changes -- All data attributes now has `tooltip` into his name +- All data attributes are now prefixed with `data-tooltip-` - Default Padding changed from `padding: 8px 21px;` to `padding: 8px 16px;` - Exported module now is `Tooltip` instead of `ReactTooltip` - - If you already have a `Tooltip` component in your application and want to explicitly declare this is `ReactTooltip`, just `import { Tooltip as ReactTooltip } from "react-tooltip"` -- CSS import is now optional, so, you can modify and/or add any styling to your floating tooltip element -- `data-tip` attribute now is `data-content` -- `getContent` prop was removed. Instead, you can directly pass dynamic content to the `content` tooltip prop, or to `data-tooltip-content` -- default behavior of tooltip now is `solid` instead of `float` +- CSS import is now optional, so you can modify and/or add any styling to your floating tooltip element +- `data-tip` attribute now is `data-tooltip-content` +- `getContent` prop was removed. Instead, you can directly pass dynamic content to the `content` tooltip prop, or to `data-tooltip-content` in the anchor element +- Default behavior of tooltip now is equivalent to V4's `solid` effect, instead of `float`. The new `float` prop can be set to achieve V4's `effect="float"`. See [Options](../options.mdx) for more details. ## New Props -- [x] classNameArrow -- [x] events - `data-events` -`['hover', 'click']` - default: `['hover']` (always an array when using as prop, even with only one option, when using as data attribute: `data-events="hover click"`) -- [x] isOpen - `boolean` (to control tooltip state) - if not used, internal state of tooltip will handle the show state -- [x] setIsOpen - `function` (to control tooltip state) - if not used, internal state of tooltip will handle the show state +- [x] `classNameArrow` +- [x] `events` (or `data-tooltip-events` on anchor element) - `['hover', 'click']` - default: `['hover']` (always an array when using as prop, even with only one option, when using as data attribute: `data-tooltip-events="hover click"`) +- [x] `isOpen` - `boolean` (to control tooltip state) - if not used, tooltip state will be handled internally +- [x] `setIsOpen` - `function` (to control tooltip state) - if not used, tooltip state will be handled internally +- [x] `position` - `{ x: number; y: number }` - similar to V4's `overridePosition` +- [x] `float` - `boolean` - used to achieve V4's `effect="float"` ## `V4` props available in `V5` -- [x] children -- [x] place - `data-place` -- [x] type - **Deprecated** | in V5 -> `variant` - `data-variant` -- [ ] effect - not implemented yet, if many users need this feature, we will work on this one. -- [x] offset - `data-offset` -- [ ] padding - **Deprecated** | in V5 -> can be easy updated by className prop -- [ ] multiline - **Deprecated** | in V5 -> this is already supported as default by `content` and `html` props -- [ ] border - **Deprecated** | in V5 -> can be easy updated by `className` prop -- [ ] borderClass - **Deprecated** | in V5 -> can be easy updated by `className` prop -- [ ] textColor - **Deprecated** | in V5 -> can be easy updated by `className` prop -- [ ] backgroundColor - **Deprecated** | in V5 -> can be easy updated by `className` prop -- [ ] borderColor - **Deprecated** | in V5 -> can be easy updated by `className` prop -- [ ] arrowColor - **Deprecated** | in V5 -> can be easy updated by `className` prop -- [ ] arrowRadius - **Deprecated** | in V5 -> can be easy updated by `className` prop -- [ ] tooltipRadius - **Deprecated** | in V5 -> can be easy updated by `className` prop -- [ ] insecure - **Deprecated** | in V5 -> CSS will be a separate file and can be imported or not -- [x] className -- [x] id -- [x] html -- [x] delayHide - `data-delay-hide` -- [ ] delayUpdate - **Deprecated** | if requested, can be implemented later -- [x] delayShow - `data-delay-show` -- [ ] event - not implemented yet, if many users need this feature, we will work on this one. -- [ ] eventOff - **Deprecated** -- [ ] isCapture - **Deprecated** -- [ ] globalEventOff - **Deprecated** -- [ ] getContent - use dynamic `content` instead -- [ ] afterShow - not implemented yet, if many users need this feature, we will work on this one. -- [ ] afterHide - not implemented yet, if many users need this feature, we will work on this one. -- [ ] overridePosition - **Deprecated** -- [ ] disable - **Deprecated** | in V5 -> state can be controlled or uncontrolled -- [ ] scrollHide - not implemented yet, if many users need this feature, we will validate if we wrok on this one. -- [ ] resizeHide - not implemented yet, if many users need this feature, we will validate if we wrok on this one. -- [x] wrapper - `data-wrapper` -- [ ] bodyMode - **Deprecated** -- [ ] clickable - **Deprecated** | Supported by default in V5 -- [ ] disableInternalStyle - **Deprecated** | in V5 -> CSS will be a separate file and can be imported or not +- [x] `children` +- [x] `place` - also available on anchor element as `data-tooltip-place` +- [ ] `type` - use `variant`. also available on anchor element as `data-tooltip-variant` +- [ ] `effect` - use `float` prop +- [x] `offset` - also available on anchor element as `data-tooltip-offset` +- [ ] `padding` - use `className` and custom CSS +- [ ] `multiline` - supported by default in `content` and `html` props +- [ ] `border` - use `className` and custom CSS +- [ ] `borderClass` - use `className` and custom CSS +- [ ] `textColor` - use `className` and custom CSS +- [ ] `backgroundColor` - use `className` and custom CSS +- [ ] `borderColor` - use `className` and custom CSS +- [ ] `arrowColor` - use `className` and custom CSS +- [ ] `arrowRadius` - use `className` and custom CSS +- [ ] `tooltipRadius` - use `className` and custom CSS +- [ ] `insecure` - CSS will be a separate file and can be imported or not +- [x] `className` +- [x] `id` +- [x] `html` +- [x] `delayHide` - also available on anchor element as `data-delay-hide` +- [ ] `delayUpdate` - if requested, can be implemented later +- [x] `delayShow` - also available on anchor element as `data-delay-show` +- [ ] `event` +- [ ] `eventOff` +- [ ] `isCapture` +- [ ] `globalEventOff` +- [ ] `getContent` - use dynamic `content` +- [ ] `afterShow` - if requested, can be implemented later +- [ ] `afterHide` - if requested, can be implemented later +- [ ] `overridePosition` - use `position` +- [ ] `disable` - state can be controlled or uncontrolled +- [ ] `scrollHide` - if requested, can be implemented later +- [ ] `resizeHide` - if requested, can be implemented later +- [x] `wrapper` - also available on anchor element as `data-tooltip-wrapper` +- [ ] `bodyMode` +- [ ] `clickable` - use controlled state to keep tooltip open +- [ ] `disableInternalStyle` - CSS will be a separate file and can be imported or not ### Detailed informations From c1d9ca35671737f1bbfb0ffbca3b6751fd219e6b Mon Sep 17 00:00:00 2001 From: Daniel Barion Date: Wed, 21 Dec 2022 10:18:24 -0300 Subject: [PATCH 17/19] refactor: use let instead of state for lastFloatPosition variable --- src/components/Tooltip/Tooltip.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/Tooltip/Tooltip.tsx b/src/components/Tooltip/Tooltip.tsx index e05857c29..8af25a5da 100644 --- a/src/components/Tooltip/Tooltip.tsx +++ b/src/components/Tooltip/Tooltip.tsx @@ -7,6 +7,8 @@ import { computeTooltipPosition } from '../../utils/compute-positions' import styles from './styles.module.css' import type { IPosition, ITooltip } from './TooltipTypes' +let lastFloatPosition: IPosition | null = null + const Tooltip = ({ // props id, @@ -40,7 +42,6 @@ const Tooltip = ({ const [inlineArrowStyles, setInlineArrowStyles] = useState({}) const [show, setShow] = useState(false) const [calculatingPosition, setCalculatingPosition] = useState(false) - const [lastFloatPosition, setLastFloatPosition] = useState(null) const { anchorRefs, setActiveAnchor: setProviderActiveAnchor } = useTooltip()(id) const [activeAnchor, setActiveAnchor] = useState>({ current: null }) @@ -147,7 +148,7 @@ const Tooltip = ({ y: mouseEvent.clientY, } handleTooltipPosition(mousePosition) - setLastFloatPosition(mousePosition) + lastFloatPosition = mousePosition } const handleClickTooltipAnchor = () => { From 6a9a01d61115cc3d43241cf273854a70006a261b Mon Sep 17 00:00:00 2001 From: gabrieljablonski Date: Wed, 21 Dec 2022 10:46:26 -0300 Subject: [PATCH 18/19] refactor: `useRef()` for `lastFloatPosition` --- src/components/Tooltip/Tooltip.tsx | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/components/Tooltip/Tooltip.tsx b/src/components/Tooltip/Tooltip.tsx index 8af25a5da..995d8d2ef 100644 --- a/src/components/Tooltip/Tooltip.tsx +++ b/src/components/Tooltip/Tooltip.tsx @@ -7,8 +7,6 @@ import { computeTooltipPosition } from '../../utils/compute-positions' import styles from './styles.module.css' import type { IPosition, ITooltip } from './TooltipTypes' -let lastFloatPosition: IPosition | null = null - const Tooltip = ({ // props id, @@ -42,6 +40,7 @@ const Tooltip = ({ const [inlineArrowStyles, setInlineArrowStyles] = useState({}) const [show, setShow] = useState(false) const [calculatingPosition, setCalculatingPosition] = useState(false) + const lastFloatPosition = useRef(null) const { anchorRefs, setActiveAnchor: setProviderActiveAnchor } = useTooltip()(id) const [activeAnchor, setActiveAnchor] = useState>({ current: null }) @@ -148,7 +147,7 @@ const Tooltip = ({ y: mouseEvent.clientY, } handleTooltipPosition(mousePosition) - lastFloatPosition = mousePosition + lastFloatPosition.current = mousePosition } const handleClickTooltipAnchor = () => { @@ -235,7 +234,7 @@ const Tooltip = ({ } if (float) { - if (lastFloatPosition) { + if (lastFloatPosition.current) { /* Without this, changes to `content`, `place`, `offset`, ..., will only trigger a position calculation after a `mousemove` event. @@ -243,7 +242,7 @@ const Tooltip = ({ To see why this matters, comment this line, run `yarn dev` and click the "Hover me!" anchor. */ - handleTooltipPosition(lastFloatPosition) + handleTooltipPosition(lastFloatPosition.current) } // if `float` is set, override regular positioning return () => null From c875241537a4432b7d308defbd06aac57888bdf9 Mon Sep 17 00:00:00 2001 From: gabrieljablonski Date: Wed, 21 Dec 2022 11:05:56 -0300 Subject: [PATCH 19/19] chore: bump project version and docs package --- docs/package.json | 2 +- docs/yarn.lock | 8 ++++---- package.json | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/package.json b/docs/package.json index ff0239210..1de8fe10e 100644 --- a/docs/package.json +++ b/docs/package.json @@ -23,7 +23,7 @@ "raw-loader": "^4.0.2", "react": "18.2.0", "react-dom": "18.2.0", - "react-tooltip": "5.2.0" + "react-tooltip": "5.3.0" }, "devDependencies": { "@docusaurus/module-type-aliases": "2.2.0", diff --git a/docs/yarn.lock b/docs/yarn.lock index 15f8f05e5..def29a93a 100644 --- a/docs/yarn.lock +++ b/docs/yarn.lock @@ -6112,10 +6112,10 @@ react-textarea-autosize@^8.3.2: use-composed-ref "^1.3.0" use-latest "^1.2.1" -react-tooltip@5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/react-tooltip/-/react-tooltip-5.2.0.tgz#e10e7de2385e8fe6bf3438739c574558b455de3b" - integrity sha512-EH6XIg2MDbMTEElSAZQVXMVeFoOhTgQuea2or0iwyzsr9v8rJf3ImMhOtq7Xe/BPlougxC+PmOibazodLdaRoA== +react-tooltip@5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/react-tooltip/-/react-tooltip-5.3.0.tgz#cba9d40396bf7c15c5d748425f76ec5c3e69b213" + integrity sha512-dE702vnPYYUPDWeFHCMzyCbJ3Ca3c160p1EQvumacTl19a0RjVJ4KHBT4XCJ1FVPLKjI6xJR0+RuSAzWyUkGow== dependencies: "@floating-ui/dom" "^1.0.4" classnames "^2.3.2" diff --git a/package.json b/package.json index e826fbe74..139bdaca0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-tooltip", - "version": "5.2.0", + "version": "5.3.0", "description": "react tooltip component", "scripts": { "dev": "node ./cli.js --env=development && node --max_old_space_size=2048 ./node_modules/rollup/dist/bin/rollup -c rollup.config.dev.js --watch",