From 3157cf65ef0e5a166bc98e9791092549f96cbd71 Mon Sep 17 00:00:00 2001 From: Robert Long Date: Tue, 14 Dec 2021 22:00:00 -0800 Subject: [PATCH] Fix tooltip behaviors on click/focus --- package.json | 2 ++ src/GridLayoutMenu.jsx | 17 +++++++--- src/OverflowMenu.jsx | 17 +++++++--- src/Tooltip.jsx | 56 +++++++++++++++++++++++++++++++ src/Tooltip.module.css | 32 ++++++++++++++++++ src/UserMenu.jsx | 17 +++++++--- src/button/Button.jsx | 75 ++++++++++++++++++++++++++---------------- yarn.lock | 31 +++++++++++++++++ 8 files changed, 204 insertions(+), 43 deletions(-) create mode 100644 src/Tooltip.jsx create mode 100644 src/Tooltip.module.css diff --git a/package.json b/package.json index 0e3d1f31..4d4198e4 100644 --- a/package.json +++ b/package.json @@ -13,10 +13,12 @@ "@react-aria/overlays": "^3.7.3", "@react-aria/select": "^3.6.0", "@react-aria/tabs": "^3.1.0", + "@react-aria/tooltip": "^3.1.3", "@react-aria/utils": "^3.10.0", "@react-stately/collections": "^3.3.4", "@react-stately/overlays": "^3.1.3", "@react-stately/select": "^3.1.3", + "@react-stately/tooltip": "^3.0.5", "@react-stately/tree": "^3.2.0", "@sentry/react": "^6.13.3", "@sentry/tracing": "^6.13.3", diff --git a/src/GridLayoutMenu.jsx b/src/GridLayoutMenu.jsx index f2b48e5a..ce358ffc 100644 --- a/src/GridLayoutMenu.jsx +++ b/src/GridLayoutMenu.jsx @@ -1,5 +1,5 @@ import React from "react"; -import { ButtonTooltip, Button } from "./button"; +import { Button } from "./button"; import { PopoverMenuTrigger } from "./PopoverMenu"; import { ReactComponent as SpotlightIcon } from "./icons/Spotlight.svg"; import { ReactComponent as FreedomIcon } from "./icons/Freedom.svg"; @@ -7,14 +7,21 @@ import { ReactComponent as CheckIcon } from "./icons/Check.svg"; import styles from "./GridLayoutMenu.module.css"; import { Menu } from "./Menu"; import { Item } from "@react-stately/collections"; +import { Tooltip, TooltipTrigger } from "./Tooltip"; export function GridLayoutMenu({ layout, setLayout }) { return ( - + + + {(props) => ( + + Layout Type + + )} + {(props) => ( diff --git a/src/OverflowMenu.jsx b/src/OverflowMenu.jsx index 94a5a6cb..25d63d71 100644 --- a/src/OverflowMenu.jsx +++ b/src/OverflowMenu.jsx @@ -1,5 +1,5 @@ import React, { useCallback } from "react"; -import { ButtonTooltip, Button } from "./button"; +import { Button } from "./button"; import { Menu } from "./Menu"; import { PopoverMenuTrigger } from "./PopoverMenu"; import { Item } from "@react-stately/collections"; @@ -9,6 +9,7 @@ import { ReactComponent as OverflowIcon } from "./icons/Overflow.svg"; import { useModalTriggerState } from "./Modal"; import { SettingsModal } from "./SettingsModal"; import { InviteModal } from "./InviteModal"; +import { Tooltip, TooltipTrigger } from "./Tooltip"; export function OverflowMenu({ roomId, @@ -37,10 +38,16 @@ export function OverflowMenu({ return ( <> - + + + {(props) => ( + + More + + )} + {(props) => ( diff --git a/src/Tooltip.jsx b/src/Tooltip.jsx new file mode 100644 index 00000000..fdc07986 --- /dev/null +++ b/src/Tooltip.jsx @@ -0,0 +1,56 @@ +import React, { forwardRef, useRef } from "react"; +import { useTooltipTriggerState } from "@react-stately/tooltip"; +import { useTooltipTrigger, useTooltip } from "@react-aria/tooltip"; +import { mergeProps } from "@react-aria/utils"; +import styles from "./Tooltip.module.css"; +import classNames from "classnames"; + +export function Tooltip({ position, state, ...props }) { + let { tooltipProps } = useTooltip(props, state); + + return ( +
+ {props.children} +
+ ); +} + +export const TooltipTrigger = forwardRef(({ children, ...rest }, ref) => { + const tooltipState = useTooltipTriggerState(rest); + const fallbackRef = useRef(); + const triggerRef = ref || fallbackRef; + const { triggerProps, tooltipProps } = useTooltipTrigger( + rest, + tooltipState, + triggerRef + ); + + if ( + !Array.isArray(children) || + children.length > 2 || + typeof children[1] !== "function" + ) { + throw new Error( + "TooltipTrigger must have two props. The first being a button and the second being a render prop." + ); + } + + const [tooltipTrigger, tooltip] = children; + + return ( +
+ + {tooltipState.isOpen && tooltip({ state: tooltipState, ...tooltipProps })} +
+ ); +}); + +TooltipTrigger.defaultProps = { + delay: 250, +}; diff --git a/src/Tooltip.module.css b/src/Tooltip.module.css new file mode 100644 index 00000000..30306c02 --- /dev/null +++ b/src/Tooltip.module.css @@ -0,0 +1,32 @@ +.tooltip { + background-color: var(--bgColor2); + position: absolute; + flex-direction: row; + justify-content: center; + align-items: center; + padding: 8px 10px; + color: var(--textColor1); + border-radius: 8px; + max-width: 135px; + width: max-content; + z-index: 1; + left: 50%; + transform: translateX(-50%); +} + +.tooltip.top { + bottom: calc(100% + 6px); +} + +.tooltip.bottom { + top: calc(100% + 6px); +} + +.tooltip.bottomLeft { + top: calc(100% + 6px); + left: -25%; +} + +.tooltipContainer { + position: relative; +} diff --git a/src/UserMenu.jsx b/src/UserMenu.jsx index b7d15883..65d0b9e8 100644 --- a/src/UserMenu.jsx +++ b/src/UserMenu.jsx @@ -1,5 +1,5 @@ import React, { useCallback, useMemo } from "react"; -import { ButtonTooltip, Button } from "./button"; +import { Button } from "./button"; import { PopoverMenuTrigger } from "./PopoverMenu"; import { ReactComponent as UserIcon } from "./icons/User.svg"; import { ReactComponent as LoginIcon } from "./icons/Login.svg"; @@ -11,6 +11,7 @@ import { useHistory, useLocation } from "react-router-dom"; import { useClient, useDisplayName } from "./ConferenceCallManagerHooks"; import { useModalTriggerState } from "./Modal"; import { ProfileModal } from "./ProfileModal"; +import { Tooltip, TooltipTrigger } from "./Tooltip"; export function UserMenu() { const location = useLocation(); @@ -77,10 +78,16 @@ export function UserMenu() { return ( <> - + + + {(props) => ( + + Profile + + )} + {(props) => ( {items.map(({ key, icon: Icon, label }) => ( diff --git a/src/button/Button.jsx b/src/button/Button.jsx index 8f1ab8f1..6458c962 100644 --- a/src/button/Button.jsx +++ b/src/button/Button.jsx @@ -8,7 +8,8 @@ import { ReactComponent as DisableVideoIcon } from "../icons/DisableVideo.svg"; import { ReactComponent as HangupIcon } from "../icons/Hangup.svg"; import { ReactComponent as ScreenshareIcon } from "../icons/Screenshare.svg"; import { useButton } from "@react-aria/button"; -import { useObjectRef } from "@react-aria/utils"; +import { mergeProps, useObjectRef } from "@react-aria/utils"; +import { Tooltip, TooltipTrigger } from "../Tooltip"; export const variantToClassName = { default: [styles.button], @@ -61,7 +62,7 @@ export const Button = forwardRef( [styles.off]: off, } )} - {...filteredButtonProps} + {...mergeProps(filteredButtonProps, rest)} ref={buttonRef} > {children} @@ -80,46 +81,64 @@ export function ButtonTooltip({ className, children }) { export function MicButton({ muted, ...rest }) { return ( - + + + {(props) => ( + + {muted ? "Unmute microphone" : "Mute microphone"} + + )} + ); } export function VideoButton({ muted, ...rest }) { return ( - + + + {(props) => ( + + {muted ? "Turn on camera" : "Turn off camera"} + + )} + ); } export function ScreenshareButton({ enabled, className, ...rest }) { return ( - + + + {(props) => ( + + {enabled ? "Stop sharing screen" : "Share screen"} + + )} + ); } export function HangupButton({ className, ...rest }) { return ( - + + + {(props) => ( + + Leave + + )} + ); } diff --git a/yarn.lock b/yarn.lock index 323d666e..f43b156b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -451,6 +451,19 @@ "@react-types/shared" "^3.10.0" "@react-types/tabs" "^3.0.1" +"@react-aria/tooltip@^3.1.3": + version "3.1.3" + resolved "https://registry.yarnpkg.com/@react-aria/tooltip/-/tooltip-3.1.3.tgz#cf967d9306170ed2ec0ed589fe1cb4cc081ddbe6" + integrity sha512-l2/BS1XBKrLpg+dovI3xy6NdCgJ5n82TS4p8vQJa7GcynI1I64R0IjOUFv0lc6ZZsr1G8Wg71SNYfmlgTrPr2w== + dependencies: + "@babel/runtime" "^7.6.2" + "@react-aria/focus" "^3.4.1" + "@react-aria/interactions" "^3.5.1" + "@react-aria/utils" "^3.8.2" + "@react-stately/tooltip" "^3.0.5" + "@react-types/shared" "^3.8.0" + "@react-types/tooltip" "^3.1.2" + "@react-aria/utils@^3.10.0", "@react-aria/utils@^3.8.2", "@react-aria/utils@^3.9.0": version "3.10.0" resolved "https://registry.yarnpkg.com/@react-aria/utils/-/utils-3.10.0.tgz#2f6f0b0ccede17241fca1cbd76978e1bf8f5a2b0" @@ -600,6 +613,16 @@ "@react-types/checkbox" "^3.2.3" "@react-types/shared" "^3.8.0" +"@react-stately/tooltip@^3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@react-stately/tooltip/-/tooltip-3.0.5.tgz#0cb716791ef3242acd810794162d651b1c47a328" + integrity sha512-rHqPSfkxbx0T0B/j+WDl4G2CfLjFeBfyaifGiIUJWHO/0Kwvh5am88VeHtuTVzC2DPEGTdtXqYns21EuJOrDlQ== + dependencies: + "@babel/runtime" "^7.6.2" + "@react-stately/overlays" "^3.1.3" + "@react-stately/utils" "^3.2.2" + "@react-types/tooltip" "^3.1.2" + "@react-stately/tree@^3.2.0": version "3.2.0" resolved "https://registry.yarnpkg.com/@react-stately/tree/-/tree-3.2.0.tgz#151c90f161c5c8339b6876f59a4f0502be08670b" @@ -688,6 +711,14 @@ dependencies: "@react-types/shared" "^3.8.0" +"@react-types/tooltip@^3.1.2": + version "3.1.2" + resolved "https://registry.yarnpkg.com/@react-types/tooltip/-/tooltip-3.1.2.tgz#a80d1ab5a37337156881a032cdaa136a9e028e32" + integrity sha512-puyiRi3IaEeKH25AErZzQKthnxk1McU+7S+Qo2kFLy3F3PyXV0cmSqvKKOhH6kU5Cw4ZnuAlNjCI0tV8PYdlYA== + dependencies: + "@react-types/overlays" "^3.5.1" + "@react-types/shared" "^3.8.0" + "@sentry/browser@6.13.3": version "6.13.3" resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-6.13.3.tgz#d4511791b1e484ad48785eba3bce291fdf115c1e"