From f57b5206223dfec6476c230b9178713d037b5125 Mon Sep 17 00:00:00 2001 From: Robert Long Date: Fri, 3 Dec 2021 16:42:29 -0800 Subject: [PATCH] Transition to modals and popovers --- package.json | 3 + src/App.jsx | 60 +++++++------- src/App.module.css | 5 -- src/GridLayoutMenu.jsx | 36 +++++++++ src/GridLayoutMenu.module.css | 8 ++ src/OverflowMenu.jsx | 31 ++++++++ src/PopoverMenu.jsx | 145 ++++++++++++++++++++++++++++++++++ src/PopoverMenu.module.css | 48 +++++++++++ src/Room.jsx | 47 ++++------- src/RoomButton.jsx | 7 +- src/UserMenu.jsx | 69 ++++++++++++++++ src/UserMenu.module.css | 19 +++++ src/icons/AddUser.svg | 4 +- src/icons/Login.svg | 3 + src/icons/Logout.svg | 3 + src/icons/Overflow.svg | 5 ++ src/icons/Settings.svg | 2 +- src/index.css | 8 ++ yarn.lock | 79 ++++++++++++++++++ 19 files changed, 507 insertions(+), 75 deletions(-) delete mode 100644 src/App.module.css create mode 100644 src/GridLayoutMenu.jsx create mode 100644 src/GridLayoutMenu.module.css create mode 100644 src/OverflowMenu.jsx create mode 100644 src/PopoverMenu.jsx create mode 100644 src/PopoverMenu.module.css create mode 100644 src/UserMenu.jsx create mode 100644 src/UserMenu.module.css create mode 100644 src/icons/Login.svg create mode 100644 src/icons/Logout.svg create mode 100644 src/icons/Overflow.svg diff --git a/package.json b/package.json index 4060320..4bdcbbb 100644 --- a/package.json +++ b/package.json @@ -9,9 +9,12 @@ "@react-aria/button": "^3.3.4", "@react-aria/dialog": "^3.1.4", "@react-aria/focus": "^3.5.0", + "@react-aria/menu": "^3.3.0", "@react-aria/overlays": "^3.7.3", "@react-aria/utils": "^3.10.0", + "@react-stately/collections": "^3.3.4", "@react-stately/overlays": "^3.1.3", + "@react-stately/tree": "^3.2.0", "@sentry/react": "^6.13.3", "@sentry/tracing": "^6.13.3", "classnames": "^2.3.1", diff --git a/src/App.jsx b/src/App.jsx index 83787dd..52150b8 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -22,8 +22,6 @@ import { Redirect, useLocation, } from "react-router-dom"; -import styles from "./App.module.css"; -import { OverlayProvider } from "@react-aria/overlays"; import * as Sentry from "@sentry/react"; import { useClient } from "./ConferenceCallManagerHooks"; import { Home } from "./Home"; @@ -50,36 +48,34 @@ export default function App() { } = useClient(homeserverUrl); return ( - - - <> - {loading ? ( -
-

Loading...

-
- ) : ( - - - - - - - - - - - - {authenticated ? ( - - ) : ( - - )} - - - )} - -
-
+ + <> + {loading ? ( +
+

Loading...

+
+ ) : ( + + + + + + + + + + + + {authenticated ? ( + + ) : ( + + )} + + + )} + +
); } diff --git a/src/App.module.css b/src/App.module.css deleted file mode 100644 index 59e0c7c..0000000 --- a/src/App.module.css +++ /dev/null @@ -1,5 +0,0 @@ -.overlayProvider { - display: flex; - flex-direction: column; - height: 100%; -} diff --git a/src/GridLayoutMenu.jsx b/src/GridLayoutMenu.jsx new file mode 100644 index 0000000..57eee1f --- /dev/null +++ b/src/GridLayoutMenu.jsx @@ -0,0 +1,36 @@ +import React, { useCallback } from "react"; +import { ButtonTooltip, HeaderButton } from "./RoomButton"; +import { Popover, PopoverMenu, PopoverMenuItem } from "./PopoverMenu"; +import { ReactComponent as SpotlightIcon } from "./icons/Spotlight.svg"; +import { ReactComponent as FreedomIcon } from "./icons/Freedom.svg"; +import { ReactComponent as CheckIcon } from "./icons/Check.svg"; +import styles from "./GridLayoutMenu.module.css"; + +export function GridLayoutMenu({ layout, setLayout }) { + const onAction = useCallback((value) => setLayout(value)); + + return ( + + + Layout Type + {layout === "spotlight" ? : } + + {(props) => ( + + + + Freedom + {layout === "freedom" && } + + + + Spotlight + {layout === "spotlight" && ( + + )} + + + )} + + ); +} diff --git a/src/GridLayoutMenu.module.css b/src/GridLayoutMenu.module.css new file mode 100644 index 0000000..8056f31 --- /dev/null +++ b/src/GridLayoutMenu.module.css @@ -0,0 +1,8 @@ +.checkIcon { + position: absolute; + right: 16px; +} + +.checkIcon * { + stroke: var(--textColor1); +} diff --git a/src/OverflowMenu.jsx b/src/OverflowMenu.jsx new file mode 100644 index 0000000..28418aa --- /dev/null +++ b/src/OverflowMenu.jsx @@ -0,0 +1,31 @@ +import React, { useCallback } from "react"; +import { ButtonTooltip, RoomButton } from "./RoomButton"; +import { Popover, PopoverMenu, PopoverMenuItem } from "./PopoverMenu"; +import { ReactComponent as SettingsIcon } from "./icons/Settings.svg"; +import { ReactComponent as AddUserIcon } from "./icons/AddUser.svg"; +import { ReactComponent as OverflowIcon } from "./icons/Overflow.svg"; + +export function OverflowMenu({ roomUrl }) { + const onAction = useCallback((e) => console.log(e)); + + return ( + + + More + + + {(props) => ( + + + + Invite people + + + + Settings + + + )} + + ); +} diff --git a/src/PopoverMenu.jsx b/src/PopoverMenu.jsx new file mode 100644 index 0000000..f5ba72a --- /dev/null +++ b/src/PopoverMenu.jsx @@ -0,0 +1,145 @@ +import React, { useRef, useState, forwardRef } from "react"; +import styles from "./PopoverMenu.module.css"; +import { useMenuTriggerState } from "@react-stately/menu"; +import { useButton } from "@react-aria/button"; +import { useMenu, useMenuItem, useMenuTrigger } from "@react-aria/menu"; +import { useTreeState } from "@react-stately/tree"; +import { Item } from "@react-stately/collections"; +import { mergeProps } from "@react-aria/utils"; +import { FocusScope } from "@react-aria/focus"; +import { useFocus } from "@react-aria/interactions"; +import { + useOverlay, + DismissButton, + useOverlayPosition, + OverlayContainer, +} from "@react-aria/overlays"; +import classNames from "classnames"; + +export function PopoverMenu({ children, placement, ...rest }) { + const popoverMenuState = useMenuTriggerState(rest); + const buttonRef = useRef(); + const { menuTriggerProps, menuProps } = useMenuTrigger( + {}, + popoverMenuState, + buttonRef + ); + + const popoverRef = useRef(); + + const { overlayProps: positionProps } = 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." + ); + } + + const [popoverTrigger, popover] = children; + + return ( +
+ + {popoverMenuState.isOpen && + popover({ + isOpen: popoverMenuState.isOpen, + onClose: popoverMenuState.close, + autoFocus: popoverMenuState.focusStrategy, + domProps: menuProps, + ref: popoverRef, + positionProps, + ...rest, + })} +
+ ); +} + +export const Popover = forwardRef((props, ref) => { + const state = useTreeState({ ...props, selectionMode: "none" }); + const menuRef = useRef(); + const { menuProps } = useMenu(props, state, menuRef); + const { overlayProps } = useOverlay( + { + onClose: props.onClose, + shouldCloseOnBlur: true, + isOpen: true, + isDismissable: true, + }, + ref + ); + + return ( + + +
+ +
    + {[...state.collection].map((item) => ( + + ))} +
+ +
+
+
+ ); +}); + +function PopoverMenuItemContainer({ item, state, onAction, onClose }) { + const ref = useRef(); + const { menuItemProps } = useMenuItem( + { + key: item.key, + isDisabled: item.isDisabled, + onAction, + onClose, + }, + state, + ref + ); + + const [isFocused, setFocused] = useState(false); + const { focusProps } = useFocus({ onFocusChange: setFocused }); + + return ( +
  • + {item.rendered} +
  • + ); +} + +export const PopoverMenuItem = Item; diff --git a/src/PopoverMenu.module.css b/src/PopoverMenu.module.css new file mode 100644 index 0000000..d42c1db --- /dev/null +++ b/src/PopoverMenu.module.css @@ -0,0 +1,48 @@ +.popover { + display: flex; + flex-direction: column; + width: 194px; + background: var(--bgColor2); + box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25); + border-radius: 8px; +} + +.popoverMenu { + width: 100%; + padding: 0; + margin: 0; + list-style: none; +} + +.popoverMenuItem { + cursor: pointer; + height: 48px; + display: flex; + align-items: center; + padding: 0 12px; + color: var(--textColor1); +} + +.popoverMenuItem > * { + margin-right: 10px; +} + +.popoverMenuItem > :last-child { + margin-right: 0; +} + +.popoverMenuItem.focused, +.popoverMenuItem:hover { + background-color: var(--bgColor3); + outline: none; +} + +.popoverMenuItem.focused:first-child, +.popoverMenuItem:hover:first-child { + border-radius: 8px 8px 0 0; +} + +.popoverMenuItem.focused:last-child, +.popoverMenuItem:hover:last-child { + border-radius: 0 0 8px 8px; +} diff --git a/src/Room.jsx b/src/Room.jsx index 49c4f71..3e90657 100644 --- a/src/Room.jsx +++ b/src/Room.jsx @@ -48,6 +48,9 @@ import { ErrorModal } from "./ErrorModal"; import { GroupCallInspector } from "./GroupCallInspector"; import * as Sentry from "@sentry/react"; import { InviteModalButton } from "./InviteModal"; +import { OverflowMenu } from "./OverflowMenu"; +import { GridLayoutMenu } from "./GridLayoutMenu"; +import { UserMenu } from "./UserMenu"; const canScreenshare = "getDisplayMedia" in navigator.mediaDevices; // There is currently a bug in Safari our our code with cloning and sending MediaStreams @@ -505,15 +508,10 @@ function InRoomView({ - - - + @@ -533,36 +531,19 @@ function InRoomView({ /> )}
    - setAudioInput(value)} - options={audioInputs.map(({ label, deviceId }) => ({ - label, - value: deviceId, - }))} - > - - - setVideoInput(value)} - options={videoInputs.map(({ label, deviceId }) => ({ - label, - value: deviceId, - }))} - > - - + + {canScreenshare && !isSafari && ( )} - + +
    { + const { buttonProps } = useButton(rest, ref); return (