From acab7359fc9dcea2fb86f03e38e3ec778d9aa208 Mon Sep 17 00:00:00 2001 From: Cristian Matteu <94987118+ChrisMattew@users.noreply.github.com> Date: Thu, 14 Nov 2024 17:49:51 +0100 Subject: [PATCH] [IOPID-2426] Tooltip component (#351) ## Short description This PR adds the `Tooltip` component ## List of changes proposed in this pull request - Created `Tooltip` component - Added `Tooltips` screen in the Example app to show the `Tooltip` behaviors ## Demo |iOS|Android| |----|-------| ||| |A11Y iOS|A11Y Android| |----|-------| ||| >[!Important] > The `left` and `right` positions are calculated after the component is rendered. This implementation causes a minor glitch where the Tooltip repositions itself to achieve a centered alignment relative to the enclosing element. > To avoid this behavior, the opacity value of the component depends on the parameters returned by the `onLayout` prop. Once the `tooltipLayout` is defined, the opacity is set to a value of 1. ## How to test Run the example app and test the `Tooltip` different implementations --------- Co-authored-by: Damiano Plebani Co-authored-by: Alice Di Rico <83651704+Ladirico@users.noreply.github.com> --- example/src/navigation/navigator.tsx | 8 + example/src/navigation/params.ts | 1 + example/src/navigation/routes.ts | 4 + example/src/pages/Tooltips.tsx | 89 ++++++++ example/yarn.lock | 263 +++++++++++++--------- src/components/index.tsx | 1 + src/components/tooltip/Arrows.tsx | 36 +++ src/components/tooltip/Tooltip.tsx | 313 ++++++++++++++++++++++++++ src/components/tooltip/index.ts | 1 + src/components/tooltip/styles.ts | 44 ++++ src/components/tooltip/utils/index.ts | 179 +++++++++++++++ src/components/tooltip/utils/types.ts | 9 + 12 files changed, 847 insertions(+), 101 deletions(-) create mode 100644 example/src/pages/Tooltips.tsx create mode 100644 src/components/tooltip/Arrows.tsx create mode 100644 src/components/tooltip/Tooltip.tsx create mode 100644 src/components/tooltip/index.ts create mode 100644 src/components/tooltip/styles.ts create mode 100644 src/components/tooltip/utils/index.ts create mode 100644 src/components/tooltip/utils/types.ts diff --git a/example/src/navigation/navigator.tsx b/example/src/navigation/navigator.tsx index 3860069a..124b3b68 100644 --- a/example/src/navigation/navigator.tsx +++ b/example/src/navigation/navigator.tsx @@ -55,6 +55,7 @@ import { TabNavigationScreen } from "../pages/TabNavigation"; import { TextInputs } from "../pages/TextInputs"; import { Toasts } from "../pages/Toasts"; import { Typography } from "../pages/Typography"; +import Tooltips from '../pages/Tooltips'; import { AppParamsList } from "./params"; import APP_ROUTES from "./routes"; @@ -426,6 +427,13 @@ const AppNavigator = () => { headerTitle: APP_ROUTES.COMPONENTS.TOASTS.title }} /> + { + const [isTopVisible, setIsTopVisible] = useState(false); + const [isBottomVisible, setIsBottomVisible] = useState(false); + const [isRightVisible, setIsRightVisible] = useState(false); + const [isLeftVisible, setIsLeftVisible] = useState(false); + const [isTopLeftVisible, setIsTopLefttVisible] = useState(false); + const [isBottomRightVisible, setIsBottomRighttVisible] = useState(false); + + return ( + + + setIsBottomVisible(false)} + closeIconAccessibilityLabel='' + > + setIsBottomVisible(true)} /> + + + + setIsTopVisible(false)} + closeIconAccessibilityLabel='' + > + setIsTopVisible(true)} /> + + + + setIsRightVisible(false)} + closeIconAccessibilityLabel='' + > + setIsRightVisible(true)} /> + + setIsLeftVisible(false)} + closeIconAccessibilityLabel='' + > + setIsLeftVisible(true)} /> + + + + + setIsTopLefttVisible(false)} + closeIconAccessibilityLabel='' + > + setIsTopLefttVisible(true)} /> + + setIsBottomRighttVisible(false)} + closeIconAccessibilityLabel='' + > + setIsBottomRighttVisible(true)} /> + + + + + ); +}; + +export default Tooltips; \ No newline at end of file diff --git a/example/yarn.lock b/example/yarn.lock index 5e003319..576b567c 100644 --- a/example/yarn.lock +++ b/example/yarn.lock @@ -30,12 +30,13 @@ "@babel/highlight" "^7.22.13" chalk "^2.4.2" -"@babel/code-frame@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.24.7.tgz#882fd9e09e8ee324e496bd040401c6f046ef4465" - integrity sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA== +"@babel/code-frame@^7.25.9": + version "7.26.2" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.26.2.tgz#4b5fab97d33338eff916235055f0ebc21e573a85" + integrity sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ== dependencies: - "@babel/highlight" "^7.24.7" + "@babel/helper-validator-identifier" "^7.25.9" + js-tokens "^4.0.0" picocolors "^1.0.0" "@babel/code-frame@^7.25.9": @@ -98,15 +99,16 @@ "@jridgewell/trace-mapping" "^0.3.17" jsesc "^2.5.1" -"@babel/generator@^7.25.0": - version "7.25.0" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.25.0.tgz#f858ddfa984350bc3d3b7f125073c9af6988f18e" - integrity sha512-3LEEcj3PVW8pW2R1SR1M89g/qrYk/m/mB/tLqn7dn4sbBUQyTqnlod+II2U4dqiGtUmkcnAmkMDralTFZttRiw== +"@babel/generator@^7.25.9": + version "7.26.2" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.26.2.tgz#87b75813bec87916210e5e01939a4c823d6bb74f" + integrity sha512-zevQbhbau95nkoxSq3f/DC/SC+EEOUZd3DYqfSkMhY2/wfSeaHV1Ew4vk8e+x8lja31IbyuUa2uQ3JONqKbysw== dependencies: - "@babel/types" "^7.25.0" + "@babel/parser" "^7.26.2" + "@babel/types" "^7.26.0" "@jridgewell/gen-mapping" "^0.3.5" "@jridgewell/trace-mapping" "^0.3.25" - jsesc "^2.5.1" + jsesc "^3.0.2" "@babel/generator@^7.25.9": version "7.26.2" @@ -267,6 +269,14 @@ dependencies: "@babel/types" "^7.22.5" +"@babel/helper-module-imports@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz#e7f8d20602ebdbf9ebbea0a0751fb0f2a4141715" + integrity sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw== + dependencies: + "@babel/traverse" "^7.25.9" + "@babel/types" "^7.25.9" + "@babel/helper-module-transforms@^7.22.5", "@babel/helper-module-transforms@^7.22.9": version "7.22.9" resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.22.9.tgz#92dfcb1fbbb2bc62529024f72d942a8c97142129" @@ -278,6 +288,15 @@ "@babel/helper-split-export-declaration" "^7.22.6" "@babel/helper-validator-identifier" "^7.22.5" +"@babel/helper-module-transforms@^7.25.9": + version "7.26.0" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz#8ce54ec9d592695e58d84cd884b7b5c6a2fdeeae" + integrity sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw== + dependencies: + "@babel/helper-module-imports" "^7.25.9" + "@babel/helper-validator-identifier" "^7.25.9" + "@babel/traverse" "^7.25.9" + "@babel/helper-optimise-call-expression@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz#f21531a9ccbff644fdd156b4077c16ff0c3f609e" @@ -297,10 +316,10 @@ resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz#dd7ee3735e8a313b9f7b05a773d892e88e6d7295" integrity sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg== -"@babel/helper-plugin-utils@^7.24.7", "@babel/helper-plugin-utils@^7.24.8": - version "7.24.8" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz#94ee67e8ec0e5d44ea7baeb51e571bd26af07878" - integrity sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg== +"@babel/helper-plugin-utils@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.9.tgz#9cbdd63a9443a2c92a725cca7ebca12cc8dd9f46" + integrity sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw== "@babel/helper-plugin-utils@^7.25.9": version "7.25.9" @@ -341,6 +360,14 @@ dependencies: "@babel/types" "^7.22.5" +"@babel/helper-simple-access@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.25.9.tgz#6d51783299884a2c74618d6ef0f86820ec2e7739" + integrity sha512-c6WHXuiaRsJTyHYLJV75t9IqsmTbItYfdj99PnzYGQZkYKvan5/2jKJ7gu31J3/BJ/A18grImSPModuyG/Eo0Q== + dependencies: + "@babel/traverse" "^7.25.9" + "@babel/types" "^7.25.9" + "@babel/helper-skip-transparent-expression-wrappers@^7.20.0", "@babel/helper-skip-transparent-expression-wrappers@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz#007f15240b5751c537c40e77abb4e89eeaaa8847" @@ -348,13 +375,13 @@ dependencies: "@babel/types" "^7.22.5" -"@babel/helper-skip-transparent-expression-wrappers@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.24.7.tgz#5f8fa83b69ed5c27adc56044f8be2b3ea96669d9" - integrity sha512-IO+DLT3LQUElMbpzlatRASEyQtfhSE0+m465v++3jyyXeBTBUjtVZg28/gHeV5mrTJqvEKhKroBGAvhW+qPHiQ== +"@babel/helper-skip-transparent-expression-wrappers@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.25.9.tgz#0b2e1b62d560d6b1954893fd2b705dc17c91f0c9" + integrity sha512-K4Du3BFa3gvyhzgPcntrkDgZzQaq6uozzcpGbOO1OEJaI+EJdqWIMTLgFgQf6lrfiDFo5FU+BxKepI9RmZqahA== dependencies: - "@babel/traverse" "^7.24.7" - "@babel/types" "^7.24.7" + "@babel/traverse" "^7.25.9" + "@babel/types" "^7.25.9" "@babel/helper-skip-transparent-expression-wrappers@^7.25.9": version "7.25.9" @@ -376,10 +403,10 @@ resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz#533f36457a25814cf1df6488523ad547d784a99f" integrity sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw== -"@babel/helper-string-parser@^7.24.8": - version "7.24.8" - resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz#5b3329c9a58803d5df425e5785865881a81ca48d" - integrity sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ== +"@babel/helper-string-parser@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz#1aabb72ee72ed35789b4bbcad3ca2862ce614e8c" + integrity sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA== "@babel/helper-string-parser@^7.25.9": version "7.25.9" @@ -396,10 +423,10 @@ resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz#9544ef6a33999343c8740fa51350f30eeaaaf193" integrity sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ== -"@babel/helper-validator-identifier@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz#75b889cfaf9e35c2aaf42cf0d72c8e91719251db" - integrity sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w== +"@babel/helper-validator-identifier@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz#24b64e2c3ec7cd3b3c547729b8d16871f22cbdc7" + integrity sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ== "@babel/helper-validator-identifier@^7.25.9": version "7.25.9" @@ -452,16 +479,6 @@ chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/highlight@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.24.7.tgz#a05ab1df134b286558aae0ed41e6c5f731bf409d" - integrity sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw== - dependencies: - "@babel/helper-validator-identifier" "^7.24.7" - chalk "^2.4.2" - js-tokens "^4.0.0" - picocolors "^1.0.0" - "@babel/parser@^7.1.0", "@babel/parser@^7.13.16", "@babel/parser@^7.14.7", "@babel/parser@^7.20.0", "@babel/parser@^7.20.7", "@babel/parser@^7.22.5", "@babel/parser@^7.22.7": version "7.22.7" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.22.7.tgz#df8cf085ce92ddbdbf668a7f186ce848c9036cae" @@ -472,10 +489,12 @@ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.0.tgz#da950e622420bf96ca0d0f2909cdddac3acd8719" integrity sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw== -"@babel/parser@^7.25.0": - version "7.25.0" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.25.0.tgz#9fdc9237504d797b6e7b8f66e78ea7f570d256ad" - integrity sha512-CzdIU9jdP0dg7HdyB+bHvDJGagUv+qtzZt5rYCWwW6tITNqV9odjp6Qu41gkG0ca5UfdDUWrKkiAnHHdGRnOrA== +"@babel/parser@^7.25.9", "@babel/parser@^7.26.2": + version "7.26.2" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.26.2.tgz#fd7b6f487cfea09889557ef5d4eeb9ff9a5abd11" + integrity sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ== + dependencies: + "@babel/types" "^7.26.0" "@babel/parser@^7.25.9", "@babel/parser@^7.26.2": version "7.26.2" @@ -617,6 +636,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-syntax-jsx@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.25.9.tgz#a34313a178ea56f1951599b929c1ceacee719290" + integrity sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-syntax-logical-assignment-operators@^7.8.3": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" @@ -673,6 +699,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-syntax-typescript@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.9.tgz#67dda2b74da43727cf21d46cf9afef23f4365399" + integrity sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-transform-arrow-functions@^7.0.0": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.22.5.tgz#e5ba566d0c58a5b2ba2a8b795450641950b71958" @@ -681,11 +714,11 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-transform-arrow-functions@^7.0.0-0": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.24.7.tgz#4f6886c11e423bd69f3ce51dbf42424a5f275514" - integrity sha512-Dt9LQs6iEY++gXUwY03DNFat5C2NbO48jj+j/bSAz6b3HgPs39qcPiYt77fDObIcFwj3/C2ICX9YMwGflUoSHQ== + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.25.9.tgz#7821d4410bee5daaadbb4cdd9a6649704e176845" + integrity sha512-6jmooXYIwn9ca5/RylZADJ+EnSxVUS5sjeJ9UPk6RWRzXCmOJCy6dqItPJFpw2cuCangPK4OYr5uhGKcmrm5Qg== dependencies: - "@babel/helper-plugin-utils" "^7.24.7" + "@babel/helper-plugin-utils" "^7.25.9" "@babel/plugin-transform-async-to-generator@^7.20.0": version "7.22.5" @@ -807,6 +840,15 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/helper-simple-access" "^7.22.5" +"@babel/plugin-transform-modules-commonjs@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.25.9.tgz#d165c8c569a080baf5467bda88df6425fc060686" + integrity sha512-dwh2Ol1jWwL2MgkCzUSOvfmKElqQcuswAZypBSUsScMXvgdT8Ekq5YA6TtqpTVWH+4903NmboMuH1o9i8Rxlyg== + dependencies: + "@babel/helper-module-transforms" "^7.25.9" + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/helper-simple-access" "^7.25.9" + "@babel/plugin-transform-named-capturing-groups-regex@^7.0.0": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.5.tgz#67fe18ee8ce02d57c855185e27e3dc959b2e991f" @@ -816,12 +858,11 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-transform-nullish-coalescing-operator@^7.0.0-0": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.24.7.tgz#1de4534c590af9596f53d67f52a92f12db984120" - integrity sha512-Ts7xQVk1OEocqzm8rHMXHlxvsfZ0cEF2yomUqpKENHWMF4zKk175Y4q8H5knJes6PgYad50uuRmt3UJuhBw8pQ== + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.25.9.tgz#bcb1b0d9e948168102d5f7104375ca21c3266949" + integrity sha512-ENfftpLZw5EItALAD4WsY/KUWvhUlZndm5GC7G3evUsVeSJB6p0pBeLQUnRnBCBx7zV0RKQjR9kCuwrsIrjWog== dependencies: - "@babel/helper-plugin-utils" "^7.24.7" - "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + "@babel/helper-plugin-utils" "^7.25.9" "@babel/plugin-transform-object-super@^7.0.0": version "7.22.5" @@ -832,13 +873,12 @@ "@babel/helper-replace-supers" "^7.22.5" "@babel/plugin-transform-optional-chaining@^7.0.0-0": - version "7.24.8" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.24.8.tgz#bb02a67b60ff0406085c13d104c99a835cdf365d" - integrity sha512-5cTOLSMs9eypEy8JUVvIKOu6NgvbJMnpG62VpIHrTmROdQ+L5mDAaI40g25k5vXti55JWNX5jCkq3HZxXBQANw== + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.25.9.tgz#e142eb899d26ef715435f201ab6e139541eee7dd" + integrity sha512-6AvV0FsLULbpnXeBjrY4dmWF8F7gf8QnvTEoO/wX/5xm/xE1Xo8oPuD3MPS+KS9f9XBEAWN7X1aWr4z9HdOr7A== dependencies: - "@babel/helper-plugin-utils" "^7.24.8" - "@babel/helper-skip-transparent-expression-wrappers" "^7.24.7" - "@babel/plugin-syntax-optional-chaining" "^7.8.3" + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/helper-skip-transparent-expression-wrappers" "^7.25.9" "@babel/plugin-transform-parameters@^7.0.0", "@babel/plugin-transform-parameters@^7.20.7": version "7.22.5" @@ -906,11 +946,11 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-transform-shorthand-properties@^7.0.0-0": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.24.7.tgz#85448c6b996e122fa9e289746140aaa99da64e73" - integrity sha512-KsDsevZMDsigzbA09+vacnLpmPH4aWjcZjXdyFKGzpplxhbeB4wYtury3vglQkg6KM/xEPKt73eCjPPf1PgXBA== + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.25.9.tgz#bb785e6091f99f826a95f9894fc16fde61c163f2" + integrity sha512-MUv6t0FhO5qHnS/W8XCbHmiRWOphNufpE1IVxhK5kuN3Td9FT1x4rx4K42s3RYdMXCXpfWkGSbCSd0Z64xA7Ng== dependencies: - "@babel/helper-plugin-utils" "^7.24.7" + "@babel/helper-plugin-utils" "^7.25.9" "@babel/plugin-transform-spread@^7.0.0": version "7.22.5" @@ -935,11 +975,11 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-transform-template-literals@^7.0.0-0": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.24.7.tgz#a05debb4a9072ae8f985bcf77f3f215434c8f8c8" - integrity sha512-AfDTQmClklHCOLxtGoP7HkeMw56k1/bTQjwsfhL6pppo/M4TOBSq+jjBUBLmV/4oeFg4GWMavIl44ZeCtmmZTw== + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.25.9.tgz#6dbd4a24e8fad024df76d1fac6a03cf413f60fe1" + integrity sha512-o97AE4syN71M/lxrCtQByzphAdlYluKPDBzDVzMmfCobUjjhAryZV0AIpRPrxN0eAkxXO6ZLEScmt+PNhj2OTw== dependencies: - "@babel/helper-plugin-utils" "^7.24.7" + "@babel/helper-plugin-utils" "^7.25.9" "@babel/plugin-transform-typescript@^7.22.5", "@babel/plugin-transform-typescript@^7.5.0": version "7.22.9" @@ -951,6 +991,17 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-typescript" "^7.22.5" +"@babel/plugin-transform-typescript@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.25.9.tgz#69267905c2b33c2ac6d8fe765e9dc2ddc9df3849" + integrity sha512-7PbZQZP50tzv2KGGnhh82GSyMB01yKY9scIjf1a+GfZCtInOWqUH5+1EBU4t9fyR5Oykkkc9vFTs4OHrhHXljQ== + dependencies: + "@babel/helper-annotate-as-pure" "^7.25.9" + "@babel/helper-create-class-features-plugin" "^7.25.9" + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/helper-skip-transparent-expression-wrappers" "^7.25.9" + "@babel/plugin-syntax-typescript" "^7.25.9" + "@babel/plugin-transform-unicode-regex@^7.0.0": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.22.5.tgz#ce7e7bb3ef208c4ff67e02a22816656256d7a183" @@ -976,7 +1027,7 @@ "@babel/helper-validator-option" "^7.22.5" "@babel/plugin-transform-flow-strip-types" "^7.22.5" -"@babel/preset-typescript@^7.13.0", "@babel/preset-typescript@^7.16.7": +"@babel/preset-typescript@^7.13.0": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.22.5.tgz#16367d8b01d640e9a507577ed4ee54e0101e51c8" integrity sha512-YbPaal9LxztSGhmndR46FmAbkJ/1fAsw293tSU+I5E5h+cnJ3d4GTwyUgGYmOXJYdGA+uNePle4qbaRzj2NISQ== @@ -987,6 +1038,17 @@ "@babel/plugin-transform-modules-commonjs" "^7.22.5" "@babel/plugin-transform-typescript" "^7.22.5" +"@babel/preset-typescript@^7.16.7": + version "7.26.0" + resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.26.0.tgz#4a570f1b8d104a242d923957ffa1eaff142a106d" + integrity sha512-NMk1IGZ5I/oHhoXEElcm+xUnL/szL6xflkFZmoEU9xj1qSJXpiS7rsspYo92B4DRCDvZn2erT5LdsCeXAKNCkg== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/helper-validator-option" "^7.25.9" + "@babel/plugin-syntax-jsx" "^7.25.9" + "@babel/plugin-transform-modules-commonjs" "^7.25.9" + "@babel/plugin-transform-typescript" "^7.25.9" + "@babel/register@^7.13.16": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/register/-/register-7.22.5.tgz#e4d8d0f615ea3233a27b5c6ada6750ee59559939" @@ -1028,14 +1090,14 @@ "@babel/parser" "^7.22.15" "@babel/types" "^7.22.15" -"@babel/template@^7.25.0": - version "7.25.0" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.25.0.tgz#e733dc3134b4fede528c15bc95e89cb98c52592a" - integrity sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q== +"@babel/template@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.25.9.tgz#ecb62d81a8a6f5dc5fe8abfc3901fc52ddf15016" + integrity sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg== dependencies: - "@babel/code-frame" "^7.24.7" - "@babel/parser" "^7.25.0" - "@babel/types" "^7.25.0" + "@babel/code-frame" "^7.25.9" + "@babel/parser" "^7.25.9" + "@babel/types" "^7.25.9" "@babel/template@^7.25.9": version "7.25.9" @@ -1062,16 +1124,16 @@ debug "^4.1.0" globals "^11.1.0" -"@babel/traverse@^7.24.7": - version "7.25.2" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.25.2.tgz#1a0a4aef53177bead359ccd0c89f4426c805b2ae" - integrity sha512-s4/r+a7xTnny2O6FcZzqgT6nE4/GHEdcqj4qAeglbUOh0TeglEfmNJFAd/OLoVtGd6ZhAO8GCVvCNUO5t/VJVQ== +"@babel/traverse@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.25.9.tgz#a50f8fe49e7f69f53de5bea7e413cd35c5e13c84" + integrity sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw== dependencies: - "@babel/code-frame" "^7.24.7" - "@babel/generator" "^7.25.0" - "@babel/parser" "^7.25.0" - "@babel/template" "^7.25.0" - "@babel/types" "^7.25.2" + "@babel/code-frame" "^7.25.9" + "@babel/generator" "^7.25.9" + "@babel/parser" "^7.25.9" + "@babel/template" "^7.25.9" + "@babel/types" "^7.25.9" debug "^4.3.1" globals "^11.1.0" @@ -1106,14 +1168,13 @@ "@babel/helper-validator-identifier" "^7.22.20" to-fast-properties "^2.0.0" -"@babel/types@^7.24.7", "@babel/types@^7.25.0", "@babel/types@^7.25.2": - version "7.25.2" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.25.2.tgz#55fb231f7dc958cd69ea141a4c2997e819646125" - integrity sha512-YTnYtra7W9e6/oAZEHj0bJehPRUlLH9/fbpT5LfB0NhQXyALCRkRs3zH9v07IYhkgpqX6Z78FnuccZr/l4Fs4Q== +"@babel/types@^7.25.9", "@babel/types@^7.26.0": + version "7.26.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.26.0.tgz#deabd08d6b753bc8e0f198f8709fb575e31774ff" + integrity sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA== dependencies: - "@babel/helper-string-parser" "^7.24.8" - "@babel/helper-validator-identifier" "^7.24.7" - to-fast-properties "^2.0.0" + "@babel/helper-string-parser" "^7.25.9" + "@babel/helper-validator-identifier" "^7.25.9" "@babel/types@^7.25.9", "@babel/types@^7.26.0": version "7.26.0" @@ -2205,9 +2266,9 @@ caniuse-lite@^1.0.30001503: integrity sha512-Vdhm5S11DaFVLlyiKu4hiUTkpZu+y1KA/rZZqVQfOD5YdDT/eQKlkt7NaE0WGOFgX32diqt9MiP9CAiFeRklaA== caniuse-lite@^1.0.30001669: - version "1.0.30001679" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001679.tgz#18c573b72f72ba70822194f6c39e7888597f9e32" - integrity sha512-j2YqID/YwpLnKzCmBOS4tlZdWprXm3ZmQLBH9ZBXFOhoxLA46fwyBvx6toCBWBmnuwUY/qB3kEU6gFx8qgCroA== + version "1.0.30001680" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001680.tgz#5380ede637a33b9f9f1fc6045ea99bd142f3da5e" + integrity sha512-rPQy70G6AGUMnbwS1z6Xg+RkHYPAi18ihs47GH0jcxIG7wArmPgY3XbS2sRdBbxJljp3thdT8BIqv9ccCypiPA== chalk@^2.0.0, chalk@^2.4.2: version "2.4.2" @@ -2481,11 +2542,11 @@ debug@^4.1.0, debug@^4.1.1, debug@^4.3.2: ms "2.1.2" debug@^4.3.1: - version "4.3.6" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.6.tgz#2ab2c38fbaffebf8aa95fdfe6d88438c7a13c52b" - integrity sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg== + version "4.3.7" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.7.tgz#87945b4151a011d76d95a198d7111c865c360a52" + integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ== dependencies: - ms "2.1.2" + ms "^2.1.3" decamelize@^1.2.0: version "1.2.0" @@ -2586,9 +2647,9 @@ electron-to-chromium@^1.4.431: integrity sha512-TSkRvbXRXD8BwhcGlZXDsbI2lRoP8dvqR7LQnqQNk9KxXBc4tG8O+rTuXgTyIpEdiqSGKEBSqrxdqEntnjNncA== electron-to-chromium@^1.5.41: - version "1.5.55" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.55.tgz#73684752aa2e1aa49cafb355a41386c6637e76a9" - integrity sha512-6maZ2ASDOTBtjt9FhqYPRnbvKU5tjG0IN9SztUOWYw2AzNDNpKJYLJmlK0/En4Hs/aiWnB+JZ+gW19PIGszgKg== + version "1.5.56" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.56.tgz#3213f369efc3a41091c3b2c05bc0f406108ac1df" + integrity sha512-7lXb9dAvimCFdvUMTyucD4mnIndt/xhRKFAlky0CyFogdnNmdPQNoHI23msF/2V4mpTxMzgMdjK4+YRlFlRQZw== emoji-regex@^8.0.0: version "8.0.0" @@ -4245,7 +4306,7 @@ ms@2.1.2: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -ms@2.1.3: +ms@2.1.3, ms@^2.1.3: version "2.1.3" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== diff --git a/src/components/index.tsx b/src/components/index.tsx index 4003d9ba..a350c1e8 100644 --- a/src/components/index.tsx +++ b/src/components/index.tsx @@ -30,4 +30,5 @@ export * from "./tag"; export * from "./textInput"; export * from "./searchInput"; export * from "./toast"; +export * from "./tooltip"; export * from "./typography"; diff --git a/src/components/tooltip/Arrows.tsx b/src/components/tooltip/Arrows.tsx new file mode 100644 index 00000000..ae26a246 --- /dev/null +++ b/src/components/tooltip/Arrows.tsx @@ -0,0 +1,36 @@ +import Svg, { Path } from 'react-native-svg'; +import React from 'react'; +import { IOColors } from '../../core'; + +export const LeftArrow = ({ color = IOColors.white }: { color?: string }) => ( + + + +); +export const RightArrow = ({ color = IOColors.white }: { color?: string }) => ( + + + +); +export const BottomArrow = ({ color = IOColors.white }: { color?: string }) => ( + + + +); +export const TopArrow = ({ color = IOColors.white }: { color?: string }) => ( + + + +); \ No newline at end of file diff --git a/src/components/tooltip/Tooltip.tsx b/src/components/tooltip/Tooltip.tsx new file mode 100644 index 00000000..543a11e4 --- /dev/null +++ b/src/components/tooltip/Tooltip.tsx @@ -0,0 +1,313 @@ +import React, { + useState, + useRef, + PropsWithChildren, + useEffect, + useCallback, + JSXElementConstructor, + useMemo, + ReactElement +} from "react"; +import { + View, + Modal, + Dimensions, + LayoutChangeEvent, + TouchableWithoutFeedback +} from "react-native"; +import { useSafeAreaInsets } from "react-native-safe-area-context"; +import { every, some } from "lodash"; +import { IOColors } from "../../core"; +import { Body, H6 } from "../typography"; +import { IconButton } from "../buttons"; +import { BottomArrow, LeftArrow, RightArrow, TopArrow } from "./Arrows"; +import { + ARROW_HEIGHT, + EMPTY_SPACE, + getArrowBoxByPlacement, + getArrowCoords, + getArrowVerticalAlignment, + getDisplayInsets, + getTooltipCoords, + getTooltipVerticalAlignment, + isDefined, + isNotZero +} from "./utils"; +import { getChildrenPosition, tooltipStyles } from "./styles"; +import { + ChildrenCoords, + DisplayInsets, + Placement, + TooltipLayout +} from "./utils/types"; + +const screenDimensions = Dimensions.get("window"); +const INITIAL_COORDS: ChildrenCoords = { + x: 0, + y: 0, + width: 0, + height: 0 +}; +const ARROWS_BY_PLACEMENT: Record< + Placement, + JSXElementConstructor<{ color: string }> +> = { + top: TopArrow, + bottom: BottomArrow, + left: LeftArrow, + right: RightArrow +}; + +type CommonProps = { + /** + * The title text displayed at the top of the tooltip. + */ + title: string; + /** + * The tooltip text content. + */ + content: string; + /** + * Controls the visibility of the tooltip. + */ + isVisible: boolean; + /** + * Initial tooltip position; can be 'top', 'bottom', 'left', or 'right'. + * @default top + */ + placement?: Placement; + /** + * Insets for adjusting tooltip position within screen boundaries. + * @default {} + */ + displayInsets?: Partial; + /** + * Accessibility label for the close icon button. + */ + closeIconAccessibilityLabel: string; + /** + * Determines whether interactions with the tooltip's children are allowed when `isVisible` is set to true. + * @default false + */ + childrenInteractionsEnabled?: boolean; + /** + * Callback function triggered when the tooltip is closed. + */ + onClose: () => void; +}; +type CloseWithTapOnBackground = { + /** + * Allows closing the tooltip by tapping outside of it. + */ + allowCloseOnBackgroundTap: true; + /** + * Accessibility label for the tooltip background mask. + */ + backgroundAccessibilityLabel: string; +}; +type CloseWithBackgroundTapDisabled = { + allowCloseOnBackgroundTap?: false; +}; +type Props = CommonProps & (CloseWithTapOnBackground | CloseWithBackgroundTapDisabled); + +/** + * Tooltip component that displays a contextual tooltip around its children. + * The tooltip position is controlled by the `placement` prop and can adjust + * dynamically if there is insufficient space. + * @param {Props} props - The component props + * + * @returns {ReactElement} A tooltip component rendered around the specified children. + */ +export const Tooltip = ({ + children, + title, + content, + placement: initialPlacement = "top", + closeIconAccessibilityLabel, + isVisible, + displayInsets = {}, + allowCloseOnBackgroundTap, + childrenInteractionsEnabled = false, + onClose +}: PropsWithChildren): ReactElement => { + const insets = useSafeAreaInsets(); + const [currentPlacement, setCurrentPlacement] = + useState(initialPlacement); + const [childrenCoords, setChildrenCoords] = useState(INITIAL_COORDS); + const [tooltipLayout, setTooltipLayout] = useState(); + const childRef = useRef(null); + const titleRef = useRef(null); + const timeoutRef = useRef>(); + + const Arrow = useMemo( + () => ARROWS_BY_PLACEMENT[currentPlacement], + [currentPlacement] + ); + const isChildrenMeasurementFinished = + every(childrenCoords, isDefined) + && some(childrenCoords, isNotZero); + const isTooltipMeasurementCompleted = isDefined(tooltipLayout); + const tooltipVisibility = { opacity: isTooltipMeasurementCompleted ? 1 : 0 }; + + /** + * This function sets the `Tooltip` children coordinates + */ + const measureChildrenCoords = useCallback(() => { + if (childRef.current && typeof childRef.current.measure === "function") { + childRef.current.measure((_, __, width, height, px, py) => { + const coords = { + x: px, + y: py, + width, + height + }; + if (every(coords, isDefined)) { + setChildrenCoords(coords); + } + }); + } + }, []); + + useEffect(() => { + if (isVisible) { + // A new measure is executed every time the `Tooltip` is visible + // This is required for use within ScrollView components. + // eslint-disable-next-line functional/immutable-data + timeoutRef.current = setTimeout(measureChildrenCoords, 100); + } else { + setChildrenCoords(INITIAL_COORDS); + setCurrentPlacement(initialPlacement); + } + + return () => { + if (isVisible) { + clearTimeout(timeoutRef.current); + } + }; + }, [isVisible, initialPlacement, measureChildrenCoords]); + + /** + * This function works with `top` and `bottom` placement and sets the current placement to their opposite value + * if in the selected one there is no space to prompt the tooltip + */ + const invertPlacementIfNeeded = useCallback( + (nativeEvent: LayoutChangeEvent["nativeEvent"]) => { + if (initialPlacement === "top") { + const hasSpace = nativeEvent.layout.y >= insets.top; + + if (!hasSpace) { + setCurrentPlacement("bottom"); + } + } + if (initialPlacement === "bottom") { + const remainingSpace = + screenDimensions.height - nativeEvent.layout.y - insets.bottom; + const tooltipMinHeight = + nativeEvent.layout.height + ARROW_HEIGHT + EMPTY_SPACE; + const hasSpace = remainingSpace >= tooltipMinHeight; + + if (!hasSpace) { + setCurrentPlacement("top"); + } + } + }, + [insets.bottom, insets.top, initialPlacement] + ); + + const handleTooltipOnLayout = useCallback( + ({ nativeEvent }: LayoutChangeEvent) => { + invertPlacementIfNeeded(nativeEvent); + setTooltipLayout(nativeEvent.layout); + }, + [invertPlacementIfNeeded] + ); + + const handleTapOnBackground = useCallback(() => { + if (allowCloseOnBackgroundTap) { + onClose(); + } + }, [allowCloseOnBackgroundTap, onClose]); + + return ( + <> + + {children} + + + + {children} + + + + + +
{title}
+ + + + {content} +
+ + + +
+ + ); +}; diff --git a/src/components/tooltip/index.ts b/src/components/tooltip/index.ts new file mode 100644 index 00000000..734bb357 --- /dev/null +++ b/src/components/tooltip/index.ts @@ -0,0 +1 @@ +export * from "./Tooltip"; \ No newline at end of file diff --git a/src/components/tooltip/styles.ts b/src/components/tooltip/styles.ts new file mode 100644 index 00000000..0443a7dd --- /dev/null +++ b/src/components/tooltip/styles.ts @@ -0,0 +1,44 @@ +import { StyleSheet } from 'react-native'; +import { IOColors } from '../../core'; +import { ChildrenCoords } from './utils/types'; + +export const tooltipStyles = StyleSheet.create({ + overlay: { + position: "absolute", + width: "100%", + height: "100%", + backgroundColor: IOColors["grey-850"], + opacity: 0.6, + zIndex: 997 + }, + childrenContainer: { + position: "absolute", + zIndex: 1000 + }, + tooltipContainer: { + position: "absolute", + paddingHorizontal: 16, + paddingVertical: 16, + backgroundColor: IOColors.white, + borderRadius: 8, + zIndex: 2000, + overflow: "visible" + }, + arrowContainer: { + position: "absolute", + display: 'flex', + zIndex: 3000 + }, + closeIcon: { + position: 'absolute', + right: 8, + top: 9 // It's been used `9` instead of `8` to fix accessibility focus order. In this way title is read before close icon. + } +}); + +export const getChildrenPosition = (childrenCoords: ChildrenCoords) => ({ + top: childrenCoords.y, + left: childrenCoords.x, + width: childrenCoords.width, + height: childrenCoords.height +}); \ No newline at end of file diff --git a/src/components/tooltip/utils/index.ts b/src/components/tooltip/utils/index.ts new file mode 100644 index 00000000..4090aa02 --- /dev/null +++ b/src/components/tooltip/utils/index.ts @@ -0,0 +1,179 @@ +import { ScaledSize } from 'react-native'; +import { IOVisualCostants } from '../../../core'; +import { ChildrenCoords, DisplayInsets, Placement } from './types'; + +export const ARROW_WIDTH = 24; +export const ARROW_HEIGHT = 14; +export const EMPTY_SPACE = 8; +const DEFAULT_INSETS: DisplayInsets = { + top: 0, + bottom: 0, + left: IOVisualCostants.appMarginDefault, + right: IOVisualCostants.appMarginDefault, +}; + +/** + * @param displayInsets custom display insets + * @returns An `object` based on `DEFAULT_INSETS` and `displayInsets` + */ +export const getDisplayInsets = ( + displayInsets: Partial +): DisplayInsets => ({ ...DEFAULT_INSETS, ...displayInsets }); + +/** + * + * @param placement The `Tooltip` placement + * @returns The `Arrow` box `width` and `height` based on `placement` value + */ +export const getArrowBoxByPlacement = (placement: Placement) => { + switch (placement) { + case 'left': + case 'right': + return { + width: ARROW_HEIGHT, + height: ARROW_WIDTH, + }; + default: + return { + height: ARROW_HEIGHT, + width: ARROW_WIDTH, + }; + } +}; + +/** + * A utility function to calculate the `Tooltip` coordinates and dimensions + * @param placement The `Tooltip` placement in relation of its children + * @param childrenCoords The measures in screen of the `Tooltip` children + * @param displayInsets The active display insets + * @param screenDimensions The dimensions of the device screen + * @returns The `Tooltip` coordinates + */ +export const getTooltipCoords = ( + placement: Placement, + childrenCoords: ChildrenCoords, + displayInsets: DisplayInsets, + screenDimensions: ScaledSize +) => { + const { width: screenWidth, height: screenHeight } = screenDimensions; + + switch (placement) { + case "top": + return { + bottom: screenHeight - childrenCoords.y + ARROW_HEIGHT + EMPTY_SPACE, + left: displayInsets.left, + width: screenWidth - displayInsets.left - displayInsets.right + }; + case "bottom": + return { + top: childrenCoords.y + childrenCoords.height + ARROW_HEIGHT + EMPTY_SPACE, + left: displayInsets.left, + width: screenWidth - displayInsets.left - displayInsets.right + }; + case "left": + return { + top: childrenCoords.y, + left: displayInsets.left, + width: + screenWidth - (screenWidth - childrenCoords.x) - ARROW_HEIGHT - displayInsets.left - EMPTY_SPACE + }; + case "right": + const elementSize = childrenCoords.width + childrenCoords.x + ARROW_HEIGHT + EMPTY_SPACE; + + return { + top: childrenCoords.y, + left: elementSize, + width: + screenWidth - + (elementSize + displayInsets.right) + }; + // TODO: provide a default center position in case of Tooltip without children + default: + return {}; + } +}; + +/** + * A utility function to calculate the `Tooltip`'s `Arrow` coordinates + * @param placement The `Arrow` placement in relation of the `Tooltip` children + * @param childrenCoords The measures in screen of the `Tooltip` children + * @param screenDimensions The active display insets + * @returns The `Tooltip`'s Arrow coordinates + */ +export const getArrowCoords = ( + placement: Placement, + childrenCoords: ChildrenCoords, + screenDimensions: ScaledSize +) => { + const { width: screenWidth, height: screenHeight } = screenDimensions; + + switch (placement) { + case "top": + return { + bottom: screenHeight - childrenCoords.y + EMPTY_SPACE, + left: childrenCoords.x + childrenCoords.width / 2 - ARROW_WIDTH / 2 + }; + case "bottom": + return { + top: childrenCoords.y + childrenCoords.height + EMPTY_SPACE, + left: childrenCoords.x + childrenCoords.width / 2 - ARROW_WIDTH / 2 + }; + case "left": + return { + top: childrenCoords.y, + left: screenWidth - (screenWidth - childrenCoords.x) - ARROW_HEIGHT - EMPTY_SPACE - 1, // FIXME -> This `-1` is necessary because of the Svg size doesn't match the box size + }; + case "right": + return { + top: childrenCoords.y, + left: childrenCoords.width + childrenCoords.x + EMPTY_SPACE + }; + default: + // TODO: provide a default center position in case of Tooltip without children + return {}; + } +}; + +/** + * A utility function to calculate the `Tooltip` vertical alignment + * @param placement The `Tooltip` placement in relation of its children + * @param childrenHeight The `Tooltip`'s children height + * @param tooltipHeight The `Tooltip`'s height + * @returns If placement is `left` or `right` it returns the vertical tranlsation to align the `Tooltip` center with its `children` center, + * otherwise `null` is returned + */ +export const getTooltipVerticalAlignment = (placement: Placement, childrenHeight: number, tooltipHeight?: number) => { + if ((placement === "left" || placement === "right") && tooltipHeight) { + return { + transform: [ + { + translateY: + -tooltipHeight / 2 + childrenHeight / 2 + } + ] + }; + } + return null; +}; + +/** + * A utility function to calculate the `Arrow` vertical alignment + * @param placement The `Tooltip` placement in relation of its children + * @param childrenHeight The `Tooltip`'s children height +*/ +export const getArrowVerticalAlignment = (placement: Placement, childrenHeight: number) => { + if (placement === "left" || placement === "right") { + return { + transform: [ + { + translateY: + -ARROW_WIDTH / 2 + childrenHeight / 2 + } + ] + }; + } + return null; +}; + +export const isDefined = (v: T) => v !== undefined; +export const isNotZero = (v: number) => v !== 0; \ No newline at end of file diff --git a/src/components/tooltip/utils/types.ts b/src/components/tooltip/utils/types.ts new file mode 100644 index 00000000..206713f4 --- /dev/null +++ b/src/components/tooltip/utils/types.ts @@ -0,0 +1,9 @@ +export type DisplayInsets = Record; +export type Placement = "top" | "bottom" | "left" | "right"; +export type ChildrenCoords = { + x: number; + y: number; + width: number; + height: number; +}; +export type TooltipLayout = ChildrenCoords; \ No newline at end of file