mirror of
https://github.com/vector-im/element-call.git
synced 2024-11-24 00:38:31 +08:00
Fix tooltip behaviors on click/focus
This commit is contained in:
parent
38eb5e7c2e
commit
3157cf65ef
@ -13,10 +13,12 @@
|
|||||||
"@react-aria/overlays": "^3.7.3",
|
"@react-aria/overlays": "^3.7.3",
|
||||||
"@react-aria/select": "^3.6.0",
|
"@react-aria/select": "^3.6.0",
|
||||||
"@react-aria/tabs": "^3.1.0",
|
"@react-aria/tabs": "^3.1.0",
|
||||||
|
"@react-aria/tooltip": "^3.1.3",
|
||||||
"@react-aria/utils": "^3.10.0",
|
"@react-aria/utils": "^3.10.0",
|
||||||
"@react-stately/collections": "^3.3.4",
|
"@react-stately/collections": "^3.3.4",
|
||||||
"@react-stately/overlays": "^3.1.3",
|
"@react-stately/overlays": "^3.1.3",
|
||||||
"@react-stately/select": "^3.1.3",
|
"@react-stately/select": "^3.1.3",
|
||||||
|
"@react-stately/tooltip": "^3.0.5",
|
||||||
"@react-stately/tree": "^3.2.0",
|
"@react-stately/tree": "^3.2.0",
|
||||||
"@sentry/react": "^6.13.3",
|
"@sentry/react": "^6.13.3",
|
||||||
"@sentry/tracing": "^6.13.3",
|
"@sentry/tracing": "^6.13.3",
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { ButtonTooltip, Button } from "./button";
|
import { Button } from "./button";
|
||||||
import { PopoverMenuTrigger } from "./PopoverMenu";
|
import { PopoverMenuTrigger } from "./PopoverMenu";
|
||||||
import { ReactComponent as SpotlightIcon } from "./icons/Spotlight.svg";
|
import { ReactComponent as SpotlightIcon } from "./icons/Spotlight.svg";
|
||||||
import { ReactComponent as FreedomIcon } from "./icons/Freedom.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 styles from "./GridLayoutMenu.module.css";
|
||||||
import { Menu } from "./Menu";
|
import { Menu } from "./Menu";
|
||||||
import { Item } from "@react-stately/collections";
|
import { Item } from "@react-stately/collections";
|
||||||
|
import { Tooltip, TooltipTrigger } from "./Tooltip";
|
||||||
|
|
||||||
export function GridLayoutMenu({ layout, setLayout }) {
|
export function GridLayoutMenu({ layout, setLayout }) {
|
||||||
return (
|
return (
|
||||||
<PopoverMenuTrigger placement="bottom right">
|
<PopoverMenuTrigger placement="bottom right">
|
||||||
|
<TooltipTrigger>
|
||||||
<Button variant="icon">
|
<Button variant="icon">
|
||||||
<ButtonTooltip>Layout Type</ButtonTooltip>
|
|
||||||
{layout === "spotlight" ? <SpotlightIcon /> : <FreedomIcon />}
|
{layout === "spotlight" ? <SpotlightIcon /> : <FreedomIcon />}
|
||||||
</Button>
|
</Button>
|
||||||
|
{(props) => (
|
||||||
|
<Tooltip position="bottom" {...props}>
|
||||||
|
Layout Type
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
</TooltipTrigger>
|
||||||
{(props) => (
|
{(props) => (
|
||||||
<Menu {...props} label="Grid layout menu" onAction={setLayout}>
|
<Menu {...props} label="Grid layout menu" onAction={setLayout}>
|
||||||
<Item key="freedom" textValue="Freedom">
|
<Item key="freedom" textValue="Freedom">
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React, { useCallback } from "react";
|
import React, { useCallback } from "react";
|
||||||
import { ButtonTooltip, Button } from "./button";
|
import { Button } from "./button";
|
||||||
import { Menu } from "./Menu";
|
import { Menu } from "./Menu";
|
||||||
import { PopoverMenuTrigger } from "./PopoverMenu";
|
import { PopoverMenuTrigger } from "./PopoverMenu";
|
||||||
import { Item } from "@react-stately/collections";
|
import { Item } from "@react-stately/collections";
|
||||||
@ -9,6 +9,7 @@ import { ReactComponent as OverflowIcon } from "./icons/Overflow.svg";
|
|||||||
import { useModalTriggerState } from "./Modal";
|
import { useModalTriggerState } from "./Modal";
|
||||||
import { SettingsModal } from "./SettingsModal";
|
import { SettingsModal } from "./SettingsModal";
|
||||||
import { InviteModal } from "./InviteModal";
|
import { InviteModal } from "./InviteModal";
|
||||||
|
import { Tooltip, TooltipTrigger } from "./Tooltip";
|
||||||
|
|
||||||
export function OverflowMenu({
|
export function OverflowMenu({
|
||||||
roomId,
|
roomId,
|
||||||
@ -37,10 +38,16 @@ export function OverflowMenu({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<PopoverMenuTrigger disableOnState>
|
<PopoverMenuTrigger disableOnState>
|
||||||
|
<TooltipTrigger>
|
||||||
<Button variant="toolbar">
|
<Button variant="toolbar">
|
||||||
<ButtonTooltip>More</ButtonTooltip>
|
|
||||||
<OverflowIcon />
|
<OverflowIcon />
|
||||||
</Button>
|
</Button>
|
||||||
|
{(props) => (
|
||||||
|
<Tooltip position="top" {...props}>
|
||||||
|
More
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
</TooltipTrigger>
|
||||||
{(props) => (
|
{(props) => (
|
||||||
<Menu {...props} label="More menu" onAction={onAction}>
|
<Menu {...props} label="More menu" onAction={onAction}>
|
||||||
<Item key="invite" textValue="Invite people">
|
<Item key="invite" textValue="Invite people">
|
||||||
|
56
src/Tooltip.jsx
Normal file
56
src/Tooltip.jsx
Normal file
@ -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 (
|
||||||
|
<div
|
||||||
|
className={classNames(styles.tooltip, styles[position || "bottom"])}
|
||||||
|
{...mergeProps(props, tooltipProps)}
|
||||||
|
>
|
||||||
|
{props.children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<div className={styles.tooltipContainer}>
|
||||||
|
<tooltipTrigger.type
|
||||||
|
{...mergeProps(triggerProps, tooltipTrigger.props, rest)}
|
||||||
|
ref={triggerRef}
|
||||||
|
/>
|
||||||
|
{tooltipState.isOpen && tooltip({ state: tooltipState, ...tooltipProps })}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
TooltipTrigger.defaultProps = {
|
||||||
|
delay: 250,
|
||||||
|
};
|
32
src/Tooltip.module.css
Normal file
32
src/Tooltip.module.css
Normal file
@ -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;
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
import React, { useCallback, useMemo } from "react";
|
import React, { useCallback, useMemo } from "react";
|
||||||
import { ButtonTooltip, Button } from "./button";
|
import { Button } from "./button";
|
||||||
import { PopoverMenuTrigger } from "./PopoverMenu";
|
import { PopoverMenuTrigger } from "./PopoverMenu";
|
||||||
import { ReactComponent as UserIcon } from "./icons/User.svg";
|
import { ReactComponent as UserIcon } from "./icons/User.svg";
|
||||||
import { ReactComponent as LoginIcon } from "./icons/Login.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 { useClient, useDisplayName } from "./ConferenceCallManagerHooks";
|
||||||
import { useModalTriggerState } from "./Modal";
|
import { useModalTriggerState } from "./Modal";
|
||||||
import { ProfileModal } from "./ProfileModal";
|
import { ProfileModal } from "./ProfileModal";
|
||||||
|
import { Tooltip, TooltipTrigger } from "./Tooltip";
|
||||||
|
|
||||||
export function UserMenu() {
|
export function UserMenu() {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
@ -77,10 +78,16 @@ export function UserMenu() {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<PopoverMenuTrigger placement="bottom right">
|
<PopoverMenuTrigger placement="bottom right">
|
||||||
|
<TooltipTrigger>
|
||||||
<Button variant="icon" className={styles.userButton}>
|
<Button variant="icon" className={styles.userButton}>
|
||||||
<ButtonTooltip>Profile</ButtonTooltip>
|
|
||||||
<UserIcon />
|
<UserIcon />
|
||||||
</Button>
|
</Button>
|
||||||
|
{(props) => (
|
||||||
|
<Tooltip position="bottomLeft" {...props}>
|
||||||
|
Profile
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
</TooltipTrigger>
|
||||||
{(props) => (
|
{(props) => (
|
||||||
<Menu {...props} label="User menu" onAction={onAction}>
|
<Menu {...props} label="User menu" onAction={onAction}>
|
||||||
{items.map(({ key, icon: Icon, label }) => (
|
{items.map(({ key, icon: Icon, label }) => (
|
||||||
|
@ -8,7 +8,8 @@ import { ReactComponent as DisableVideoIcon } from "../icons/DisableVideo.svg";
|
|||||||
import { ReactComponent as HangupIcon } from "../icons/Hangup.svg";
|
import { ReactComponent as HangupIcon } from "../icons/Hangup.svg";
|
||||||
import { ReactComponent as ScreenshareIcon } from "../icons/Screenshare.svg";
|
import { ReactComponent as ScreenshareIcon } from "../icons/Screenshare.svg";
|
||||||
import { useButton } from "@react-aria/button";
|
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 = {
|
export const variantToClassName = {
|
||||||
default: [styles.button],
|
default: [styles.button],
|
||||||
@ -61,7 +62,7 @@ export const Button = forwardRef(
|
|||||||
[styles.off]: off,
|
[styles.off]: off,
|
||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
{...filteredButtonProps}
|
{...mergeProps(filteredButtonProps, rest)}
|
||||||
ref={buttonRef}
|
ref={buttonRef}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
@ -80,46 +81,64 @@ export function ButtonTooltip({ className, children }) {
|
|||||||
|
|
||||||
export function MicButton({ muted, ...rest }) {
|
export function MicButton({ muted, ...rest }) {
|
||||||
return (
|
return (
|
||||||
|
<TooltipTrigger>
|
||||||
<Button variant="toolbar" {...rest} off={muted}>
|
<Button variant="toolbar" {...rest} off={muted}>
|
||||||
<ButtonTooltip>
|
|
||||||
{muted ? "Unmute microphone" : "Mute microphone"}
|
|
||||||
</ButtonTooltip>
|
|
||||||
{muted ? <MuteMicIcon /> : <MicIcon />}
|
{muted ? <MuteMicIcon /> : <MicIcon />}
|
||||||
</Button>
|
</Button>
|
||||||
|
{(props) => (
|
||||||
|
<Tooltip position="top" {...props}>
|
||||||
|
{muted ? "Unmute microphone" : "Mute microphone"}
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
</TooltipTrigger>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function VideoButton({ muted, ...rest }) {
|
export function VideoButton({ muted, ...rest }) {
|
||||||
return (
|
return (
|
||||||
|
<TooltipTrigger>
|
||||||
<Button variant="toolbar" {...rest} off={muted}>
|
<Button variant="toolbar" {...rest} off={muted}>
|
||||||
<ButtonTooltip>
|
|
||||||
{muted ? "Turn on camera" : "Turn off camera"}
|
|
||||||
</ButtonTooltip>
|
|
||||||
{muted ? <DisableVideoIcon /> : <VideoIcon />}
|
{muted ? <DisableVideoIcon /> : <VideoIcon />}
|
||||||
</Button>
|
</Button>
|
||||||
|
{(props) => (
|
||||||
|
<Tooltip position="top" {...props}>
|
||||||
|
{muted ? "Turn on camera" : "Turn off camera"}
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
</TooltipTrigger>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ScreenshareButton({ enabled, className, ...rest }) {
|
export function ScreenshareButton({ enabled, className, ...rest }) {
|
||||||
return (
|
return (
|
||||||
|
<TooltipTrigger>
|
||||||
<Button variant="toolbar" {...rest} on={enabled}>
|
<Button variant="toolbar" {...rest} on={enabled}>
|
||||||
<ButtonTooltip>
|
|
||||||
{enabled ? "Stop sharing screen" : "Share screen"}
|
|
||||||
</ButtonTooltip>
|
|
||||||
<ScreenshareIcon />
|
<ScreenshareIcon />
|
||||||
</Button>
|
</Button>
|
||||||
|
{(props) => (
|
||||||
|
<Tooltip position="top" {...props}>
|
||||||
|
{enabled ? "Stop sharing screen" : "Share screen"}
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
</TooltipTrigger>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function HangupButton({ className, ...rest }) {
|
export function HangupButton({ className, ...rest }) {
|
||||||
return (
|
return (
|
||||||
|
<TooltipTrigger>
|
||||||
<Button
|
<Button
|
||||||
variant="toolbar"
|
variant="toolbar"
|
||||||
className={classNames(styles.hangupButton, className)}
|
className={classNames(styles.hangupButton, className)}
|
||||||
{...rest}
|
{...rest}
|
||||||
>
|
>
|
||||||
<ButtonTooltip>Leave</ButtonTooltip>
|
|
||||||
<HangupIcon />
|
<HangupIcon />
|
||||||
</Button>
|
</Button>
|
||||||
|
{(props) => (
|
||||||
|
<Tooltip position="top" {...props}>
|
||||||
|
Leave
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
</TooltipTrigger>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
31
yarn.lock
31
yarn.lock
@ -451,6 +451,19 @@
|
|||||||
"@react-types/shared" "^3.10.0"
|
"@react-types/shared" "^3.10.0"
|
||||||
"@react-types/tabs" "^3.0.1"
|
"@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":
|
"@react-aria/utils@^3.10.0", "@react-aria/utils@^3.8.2", "@react-aria/utils@^3.9.0":
|
||||||
version "3.10.0"
|
version "3.10.0"
|
||||||
resolved "https://registry.yarnpkg.com/@react-aria/utils/-/utils-3.10.0.tgz#2f6f0b0ccede17241fca1cbd76978e1bf8f5a2b0"
|
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/checkbox" "^3.2.3"
|
||||||
"@react-types/shared" "^3.8.0"
|
"@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":
|
"@react-stately/tree@^3.2.0":
|
||||||
version "3.2.0"
|
version "3.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/@react-stately/tree/-/tree-3.2.0.tgz#151c90f161c5c8339b6876f59a4f0502be08670b"
|
resolved "https://registry.yarnpkg.com/@react-stately/tree/-/tree-3.2.0.tgz#151c90f161c5c8339b6876f59a4f0502be08670b"
|
||||||
@ -688,6 +711,14 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@react-types/shared" "^3.8.0"
|
"@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":
|
"@sentry/browser@6.13.3":
|
||||||
version "6.13.3"
|
version "6.13.3"
|
||||||
resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-6.13.3.tgz#d4511791b1e484ad48785eba3bce291fdf115c1e"
|
resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-6.13.3.tgz#d4511791b1e484ad48785eba3bce291fdf115c1e"
|
||||||
|
Loading…
Reference in New Issue
Block a user