Fix button tooltips

This commit is contained in:
Robert Long 2022-01-20 13:03:54 -08:00
parent abae58489c
commit d7d38c1ba9
8 changed files with 105 additions and 174 deletions

View file

@ -1,32 +1,46 @@
import React, { forwardRef } from "react";
import React, { forwardRef, useRef } from "react";
import { useTooltipTriggerState } from "@react-stately/tooltip";
import { FocusableProvider } from "@react-aria/focus";
import { useTooltipTrigger, useTooltip } from "@react-aria/tooltip";
import { mergeProps, useObjectRef } from "@react-aria/utils";
import styles from "./Tooltip.module.css";
import classNames from "classnames";
import { OverlayContainer, useOverlayPosition } from "@react-aria/overlays";
export function Tooltip({ position, state, ...props }) {
let { tooltipProps } = useTooltip(props, state);
export const Tooltip = forwardRef(
({ position, state, className, ...props }, ref) => {
let { tooltipProps } = useTooltip(props, state);
return (
<div
className={classNames(styles.tooltip, styles[position || "bottom"])}
{...mergeProps(props, tooltipProps)}
>
{props.children}
</div>
);
}
return (
<div
className={classNames(styles.tooltip, className)}
{...mergeProps(props, tooltipProps)}
ref={ref}
>
{props.children}
</div>
);
}
);
export const TooltipTrigger = forwardRef(({ children, ...rest }, ref) => {
const tooltipState = useTooltipTriggerState(rest);
const triggerRef = useObjectRef(ref);
const overlayRef = useRef();
const { triggerProps, tooltipProps } = useTooltipTrigger(
rest,
tooltipState,
triggerRef
);
const { overlayProps } = useOverlayPosition({
placement: rest.placement || "top",
targetRef: triggerRef,
overlayRef,
isOpen: tooltipState.isOpen,
offset: 5,
});
if (
!Array.isArray(children) ||
children.length > 2 ||
@ -40,13 +54,20 @@ export const TooltipTrigger = forwardRef(({ children, ...rest }, ref) => {
const [tooltipTrigger, tooltip] = children;
return (
<div className={styles.tooltipContainer}>
<tooltipTrigger.type
{...mergeProps(triggerProps, tooltipTrigger.props, rest)}
ref={triggerRef}
/>
{tooltipState.isOpen && tooltip({ state: tooltipState, ...tooltipProps })}
</div>
<FocusableProvider ref={triggerRef} {...triggerProps}>
{<tooltipTrigger.type {...mergeProps(tooltipTrigger.props, rest)} />}
{tooltipState.isOpen && (
<OverlayContainer>
<Tooltip
state={tooltipState}
{...mergeProps(tooltipProps, overlayProps)}
ref={overlayRef}
>
{tooltip()}
</Tooltip>
</OverlayContainer>
)}
</FocusableProvider>
);
});

View file

@ -1,6 +1,5 @@
.tooltip {
background-color: var(--bgColor2);
position: absolute;
flex-direction: row;
justify-content: center;
align-items: center;
@ -9,25 +8,5 @@
border-radius: 8px;
max-width: 135px;
width: max-content;
z-index: 1;
left: 50%;
transform: translateX(-50%);
text-align: center;
}
.tooltip.top {
bottom: calc(100% + 6px);
}
.tooltip.bottom {
top: calc(100% + 6px);
}
.tooltip.bottomLeft {
top: calc(100% + 6px);
left: -25%;
}
.tooltipContainer {
position: relative;
}

View file

@ -62,7 +62,7 @@ export function UserMenu({
return (
<PopoverMenuTrigger placement="bottom right">
<TooltipTrigger>
<TooltipTrigger placement="bottom left">
<Button variant="icon" className={styles.userButton}>
{isAuthenticated && !isPasswordlessUser ? (
<Avatar
@ -75,11 +75,7 @@ export function UserMenu({
<UserIcon />
)}
</Button>
{(props) => (
<Tooltip position="bottomLeft" {...props}>
Profile
</Tooltip>
)}
{() => "Profile"}
</TooltipTrigger>
{(props) => (
<Menu {...props} label="User menu" onAction={onAction}>

View file

@ -76,25 +76,13 @@ export const Button = forwardRef(
}
);
export function ButtonTooltip({ className, children }) {
return (
<div className={classNames(styles.buttonTooltip, className)}>
{children}
</div>
);
}
export function MicButton({ muted, ...rest }) {
return (
<TooltipTrigger>
<Button variant="toolbar" {...rest} off={muted}>
{muted ? <MuteMicIcon /> : <MicIcon />}
</Button>
{(props) => (
<Tooltip position="top" {...props}>
{muted ? "Unmute microphone" : "Mute microphone"}
</Tooltip>
)}
{() => (muted ? "Unmute microphone" : "Mute microphone")}
</TooltipTrigger>
);
}
@ -105,11 +93,7 @@ export function VideoButton({ muted, ...rest }) {
<Button variant="toolbar" {...rest} off={muted}>
{muted ? <DisableVideoIcon /> : <VideoIcon />}
</Button>
{(props) => (
<Tooltip position="top" {...props}>
{muted ? "Turn on camera" : "Turn off camera"}
</Tooltip>
)}
{() => (muted ? "Turn on camera" : "Turn off camera")}
</TooltipTrigger>
);
}
@ -120,11 +104,7 @@ export function ScreenshareButton({ enabled, className, ...rest }) {
<Button variant="toolbar" {...rest} on={enabled}>
<ScreenshareIcon />
</Button>
{(props) => (
<Tooltip position="top" {...props}>
{enabled ? "Stop sharing screen" : "Share screen"}
</Tooltip>
)}
{() => (enabled ? "Stop sharing screen" : "Share screen")}
</TooltipTrigger>
);
}
@ -139,11 +119,7 @@ export function HangupButton({ className, ...rest }) {
>
<HangupIcon />
</Button>
{(props) => (
<Tooltip position="top" {...props}>
Leave
</Tooltip>
)}
{() => "Leave"}
</TooltipTrigger>
);
}

View file

@ -100,35 +100,6 @@ limitations under the License.
fill: #21262c;
}
.buttonTooltip {
display: none;
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;
}
.buttonTooltip.bottomRight {
right: 0;
}
.toolbarButton:hover .buttonTooltip {
display: flex;
bottom: calc(100% + 6px);
}
.iconButton:hover .buttonTooltip {
display: flex;
top: calc(100% + 6px);
}
.secondary,
.copyButton {
color: #0dbd8b;

View file

@ -1,72 +1,68 @@
import React, { useRef } from "react";
import React, { forwardRef, useRef } from "react";
import styles from "./PopoverMenu.module.css";
import { useMenuTriggerState } from "@react-stately/menu";
import { useMenuTrigger } from "@react-aria/menu";
import { OverlayContainer, useOverlayPosition } from "@react-aria/overlays";
import { mergeProps, useObjectRef } from "@react-aria/utils";
import classNames from "classnames";
import { Popover } from "./Popover";
export function PopoverMenuTrigger({
children,
placement,
className,
disableOnState,
...rest
}) {
const popoverMenuState = useMenuTriggerState(rest);
const buttonRef = useRef();
const { menuTriggerProps, menuProps } = useMenuTrigger(
{},
popoverMenuState,
buttonRef
);
export const PopoverMenuTrigger = forwardRef(
({ children, placement, className, disableOnState, ...rest }, ref) => {
const popoverMenuState = useMenuTriggerState(rest);
const buttonRef = useObjectRef(ref);
const { menuTriggerProps, menuProps } = useMenuTrigger(
{},
popoverMenuState,
buttonRef
);
const popoverRef = useRef();
const popoverRef = useRef();
const { overlayProps } = useOverlayPosition({
targetRef: buttonRef,
overlayRef: popoverRef,
placement: placement || "top",
offset: 5,
isOpen: popoverMenuState.isOpen,
});
const { overlayProps } = useOverlayPosition({
targetRef: buttonRef,
overlayRef: popoverRef,
placement: placement || "top",
offset: 5,
isOpen: popoverMenuState.isOpen,
});
if (
!Array.isArray(children) ||
children.length > 2 ||
typeof children[1] !== "function"
) {
throw new Error(
"PopoverMenu must have two props. The first being a button and the second being a render prop."
if (
!Array.isArray(children) ||
children.length > 2 ||
typeof children[1] !== "function"
) {
throw new Error(
"PopoverMenu must have two props. The first being a button and the second being a render prop."
);
}
const [popoverTrigger, popoverMenu] = children;
return (
<div className={classNames(styles.popoverMenuTrigger, className)}>
<popoverTrigger.type
{...mergeProps(popoverTrigger.props, menuTriggerProps)}
on={!disableOnState && popoverMenuState.isOpen}
ref={buttonRef}
/>
{popoverMenuState.isOpen && (
<OverlayContainer>
<Popover
{...overlayProps}
isOpen={popoverMenuState.isOpen}
onClose={popoverMenuState.close}
ref={popoverRef}
>
{popoverMenu({
...menuProps,
autoFocus: popoverMenuState.focusStrategy,
onClose: popoverMenuState.close,
})}
</Popover>
</OverlayContainer>
)}
</div>
);
}
const [popoverTrigger, popoverMenu] = children;
return (
<div className={classNames(styles.popoverMenuTrigger, className)}>
<popoverTrigger.type
{...popoverTrigger.props}
{...menuTriggerProps}
on={!disableOnState && popoverMenuState.isOpen}
ref={buttonRef}
/>
{popoverMenuState.isOpen && (
<OverlayContainer>
<Popover
{...overlayProps}
isOpen={popoverMenuState.isOpen}
onClose={popoverMenuState.close}
ref={popoverRef}
>
{popoverMenu({
...menuProps,
autoFocus: popoverMenuState.focusStrategy,
onClose: popoverMenuState.close,
})}
</Popover>
</OverlayContainer>
)}
</div>
);
}
);

View file

@ -16,11 +16,7 @@ export function GridLayoutMenu({ layout, setLayout }) {
<Button variant="icon">
{layout === "spotlight" ? <SpotlightIcon /> : <FreedomIcon />}
</Button>
{(props) => (
<Tooltip position="bottom" {...props}>
Layout Type
</Tooltip>
)}
{() => "Layout Type"}
</TooltipTrigger>
{(props) => (
<Menu {...props} label="Grid layout menu" onAction={setLayout}>

View file

@ -38,15 +38,11 @@ export function OverflowMenu({
return (
<>
<PopoverMenuTrigger disableOnState>
<TooltipTrigger>
<TooltipTrigger placement="top">
<Button variant="toolbar">
<OverflowIcon />
</Button>
{(props) => (
<Tooltip position="top" {...props}>
More
</Tooltip>
)}
{() => "More"}
</TooltipTrigger>
{(props) => (
<Menu {...props} label="More menu" onAction={onAction}>