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 (