diff --git a/src/CopyButton.jsx b/src/CopyButton.jsx deleted file mode 100644 index 7227617..0000000 --- a/src/CopyButton.jsx +++ /dev/null @@ -1,37 +0,0 @@ -import React from "react"; -import { useButton } from "@react-aria/button"; -import useClipboard from "react-use-clipboard"; -import { ReactComponent as CheckIcon } from "./icons/Check.svg"; -import { ReactComponent as CopyIcon } from "./icons/Copy.svg"; -import classNames from "classnames"; -import styles from "./CopyButton.module.css"; - -export function CopyButton({ value, className, children, ...rest }) { - const [isCopied, setCopied] = useClipboard(value, { successDuration: 3000 }); - const { buttonProps } = useButton({ - onPress: () => setCopied(), - }); - - return ( - - ); -} diff --git a/src/CopyButton.module.css b/src/CopyButton.module.css deleted file mode 100644 index c4826b5..0000000 --- a/src/CopyButton.module.css +++ /dev/null @@ -1,43 +0,0 @@ -.copyButton { - position: relative; - display: flex; - justify-content: center; - align-items: center; - background-color: transparent; - cursor: pointer; - border: 2px solid #0dbd8b; - border-radius: 8px; - color: #0dbd8b; - width: 100%; - height: 40px; - transition: border-color 250ms, background-color 250ms; - padding: 0 20px; -} - -.copyButton span { - font-weight: 600; - font-size: 15px; - margin-right: 10px; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - flex: 1; -} - -.copyButton svg { - flex-shrink: 0; -} - -.copyButton:not(.copied) svg * { - fill: #0dbd8b; -} - -.copyButton.copied { - border-color: transparent; - background-color: #0dbd8b; - color: white; -} - -.copyButton.copied svg * { - stroke: white; -} diff --git a/src/GridLayoutMenu.jsx b/src/GridLayoutMenu.jsx index a5d5665..f2b48e5 100644 --- a/src/GridLayoutMenu.jsx +++ b/src/GridLayoutMenu.jsx @@ -1,5 +1,5 @@ -import React, { useCallback } from "react"; -import { ButtonTooltip, HeaderButton } from "./RoomButton"; +import React from "react"; +import { ButtonTooltip, Button } from "./button"; import { PopoverMenuTrigger } from "./PopoverMenu"; import { ReactComponent as SpotlightIcon } from "./icons/Spotlight.svg"; import { ReactComponent as FreedomIcon } from "./icons/Freedom.svg"; @@ -11,10 +11,10 @@ import { Item } from "@react-stately/collections"; export function GridLayoutMenu({ layout, setLayout }) { return ( - + {(props) => ( diff --git a/src/Header.jsx b/src/Header.jsx index 6486035..5cd30d7 100644 --- a/src/Header.jsx +++ b/src/Header.jsx @@ -1,11 +1,11 @@ import classNames from "classnames"; -import React from "react"; +import React, { useRef } from "react"; import { Link } from "react-router-dom"; import styles from "./Header.module.css"; import { ReactComponent as LogoIcon } from "./Logo.svg"; import { ReactComponent as VideoIcon } from "./icons/Video.svg"; import { ReactComponent as ArrowLeftIcon } from "./icons/ArrowLeft.svg"; -import { HeaderDropdownItem, UserMenu } from "./RoomButton"; +import { useButton } from "@react-aria/button"; export function Header({ children, className, ...rest }) { return ( @@ -56,25 +56,13 @@ export function RoomHeaderInfo({ roomName }) { ); } -export function RoomSetupHeaderInfo({ onBack, roomName }) { +export function RoomSetupHeaderInfo({ roomName, ...rest }) { + const ref = useRef(); + const { buttonProps } = useButton(rest, ref); return ( - ); } - -export function UserDropdownMenu({ userName, signedIn, onLogout }) { - if (!signedIn) { - return null; - } - - return ( - - - Sign Out - - - ); -} diff --git a/src/Home.jsx b/src/Home.jsx index c335b2e..91439cf 100644 --- a/src/Home.jsx +++ b/src/Home.jsx @@ -23,7 +23,7 @@ import { import { Header, HeaderLogo, LeftNav, RightNav } from "./Header"; import ColorHash from "color-hash"; import styles from "./Home.module.css"; -import { FieldRow, InputField, Button, ErrorMessage } from "./Input"; +import { FieldRow, InputField, ErrorMessage } from "./Input"; import { Center, Content, Modal } from "./Layout"; import { GroupCallIntent, @@ -31,6 +31,7 @@ import { } from "matrix-js-sdk/src/browser-index"; import { Facepile } from "./Facepile"; import { UserMenu } from "./UserMenu"; +import { Button } from "./button"; const colorHash = new ColorHash({ lightness: 0.3 }); diff --git a/src/Input.jsx b/src/Input.jsx index 5b523d5..3acc9e8 100644 --- a/src/Input.jsx +++ b/src/Input.jsx @@ -44,18 +44,6 @@ export const InputField = forwardRef( } ); -export const Button = forwardRef(({ className, children, ...rest }, ref) => { - return ( - - ); -}); - export function ErrorMessage({ children }) { return

{children}

; } diff --git a/src/Input.module.css b/src/Input.module.css index cdbd654..8be140e 100644 --- a/src/Input.module.css +++ b/src/Input.module.css @@ -151,29 +151,6 @@ border: 1.5px solid var(--inputBorderColorFocused) !important; } -.button { - vertical-align: middle; - border: 0; - border-radius: 8px; - font-size: 14px; - color: #fff; - background-color: var(--primaryColor); - width: auto; - padding: 7px 15px; - cursor: pointer; - display: inline-block; - -webkit-box-sizing: border-box; - box-sizing: border-box; - text-align: center; - font-weight: 700; -} - -.button:hover { -} - -.button:active { -} - .errorMessage { margin: 0; font-size: 13px; diff --git a/src/InviteModal.jsx b/src/InviteModal.jsx index 18c35ac..afe531a 100644 --- a/src/InviteModal.jsx +++ b/src/InviteModal.jsx @@ -1,6 +1,6 @@ import React from "react"; import { Modal, ModalContent } from "./Modal"; -import { CopyButton } from "./CopyButton"; +import { CopyButton } from "./button"; export function InviteModal({ roomUrl, ...rest }) { return ( diff --git a/src/LoginPage.jsx b/src/LoginPage.jsx index 0b07823..cf3bc7b 100644 --- a/src/LoginPage.jsx +++ b/src/LoginPage.jsx @@ -17,8 +17,9 @@ limitations under the License. import React, { useCallback, useRef, useState } from "react"; import { useHistory, useLocation, Link } from "react-router-dom"; import { Header, HeaderLogo, LeftNav } from "./Header"; -import { FieldRow, InputField, Button, ErrorMessage } from "./Input"; +import { FieldRow, InputField, ErrorMessage } from "./Input"; import { Center, Content, Info, Modal } from "./Layout"; +import { Button } from "./button"; export function LoginPage({ onLogin }) { const [homeserver, setHomeServer] = useState( diff --git a/src/OverflowMenu.jsx b/src/OverflowMenu.jsx index 16b65e9..982ae5e 100644 --- a/src/OverflowMenu.jsx +++ b/src/OverflowMenu.jsx @@ -1,5 +1,5 @@ import React, { useCallback } from "react"; -import { ButtonTooltip, RoomButton } from "./RoomButton"; +import { ButtonTooltip, Button } from "./button"; import { Menu } from "./Menu"; import { PopoverMenuTrigger } from "./PopoverMenu"; import { Item } from "@react-stately/collections"; @@ -37,10 +37,10 @@ export function OverflowMenu({ return ( <> - + {(props) => ( diff --git a/src/RegisterPage.jsx b/src/RegisterPage.jsx index 1930ea9..2b21afe 100644 --- a/src/RegisterPage.jsx +++ b/src/RegisterPage.jsx @@ -17,8 +17,9 @@ limitations under the License. import React, { useCallback, useRef, useState } from "react"; import { useHistory, useLocation, Link } from "react-router-dom"; import { Header, LeftNav, HeaderLogo } from "./Header"; -import { FieldRow, InputField, Button, ErrorMessage } from "./Input"; +import { FieldRow, InputField, ErrorMessage } from "./Input"; import { Center, Content, Info, Modal } from "./Layout"; +import { Button } from "./button"; export function RegisterPage({ onRegister }) { const registerUsernameRef = useRef(); diff --git a/src/Room.jsx b/src/Room.jsx index a6e8261..e728a72 100644 --- a/src/Room.jsx +++ b/src/Room.jsx @@ -18,11 +18,13 @@ import React, { useCallback, useEffect, useMemo, useState } from "react"; import styles from "./Room.module.css"; import { useLocation, useParams, useHistory, Link } from "react-router-dom"; import { + Button, + CopyButton, HangupButton, MicButton, VideoButton, ScreenshareButton, -} from "./RoomButton"; +} from "./button"; import { Header, LeftNav, @@ -30,7 +32,6 @@ import { RoomHeaderInfo, RoomSetupHeaderInfo, } from "./Header"; -import { Button } from "./Input"; import { GroupCallState } from "matrix-js-sdk/src/webrtc/groupCall"; import VideoGrid, { useVideoGridLayout, @@ -47,7 +48,6 @@ import * as Sentry from "@sentry/react"; import { OverflowMenu } from "./OverflowMenu"; import { GridLayoutMenu } from "./GridLayoutMenu"; import { UserMenu } from "./UserMenu"; -import { CopyButton } from "./CopyButton"; const canScreenshare = "getDisplayMedia" in navigator.mediaDevices; // There is currently a bug in Safari our our code with cloning and sending MediaStreams @@ -276,7 +276,7 @@ function RoomSetupView({
history.goBack()} + onPress={() => history.goBack()} roomName={roomName} /> @@ -310,7 +310,7 @@ function RoomSetupView({ diff --git a/src/RoomButton.jsx b/src/RoomButton.jsx deleted file mode 100644 index 977dbe7..0000000 --- a/src/RoomButton.jsx +++ /dev/null @@ -1,255 +0,0 @@ -import React, { useRef, useState, useEffect, forwardRef } from "react"; -import classNames from "classnames"; -import styles from "./RoomButton.module.css"; -import { ReactComponent as MicIcon } from "./icons/Mic.svg"; -import { ReactComponent as MuteMicIcon } from "./icons/MuteMic.svg"; -import { ReactComponent as VideoIcon } from "./icons/Video.svg"; -import { ReactComponent as DisableVideoIcon } from "./icons/DisableVideo.svg"; -import { ReactComponent as HangupIcon } from "./icons/Hangup.svg"; -import { ReactComponent as SettingsIcon } from "./icons/Settings.svg"; -import { ReactComponent as FreedomIcon } from "./icons/Freedom.svg"; -import { ReactComponent as SpotlightIcon } from "./icons/Spotlight.svg"; -import { ReactComponent as ScreenshareIcon } from "./icons/Screenshare.svg"; -import { ReactComponent as ChevronIcon } from "./icons/Chevron.svg"; -import { ReactComponent as UserIcon } from "./icons/User.svg"; -import { ReactComponent as CheckIcon } from "./icons/Check.svg"; -import { useButton } from "@react-aria/button"; - -export const RoomButton = forwardRef( - ({ on, off, className, children, ...rest }, ref) => { - const { buttonProps } = useButton(rest, ref); - return ( - - ); - } -); - -export function DropdownButton({ onChange, options, value, children }) { - const buttonRef = useRef(); - const [open, setOpen] = useState(false); - - useEffect(() => { - function onClick() { - if (open) { - setOpen(false); - } - } - - window.addEventListener("click", onClick); - - return () => { - window.removeEventListener("click", onClick); - }; - }, [open]); - - return ( -
- {children} - - {open && ( -
-
    - {options.map((item) => ( -
  • onChange(item)} - > - {item.label} -
  • - ))} -
-
- )} -
- ); -} - -export function MicButton({ muted, ...rest }) { - return ( - - - {muted ? "Unmute microphone" : "Mute microphone"} - - {muted ? : } - - ); -} - -export function VideoButton({ muted, ...rest }) { - return ( - - - {muted ? "Turn on camera" : "Turn off camera"} - - {muted ? : } - - ); -} - -export function ScreenshareButton({ enabled, className, ...rest }) { - return ( - - - {enabled ? "Stop sharing screen" : "Share screen"} - - - - ); -} - -export function HangupButton({ className, ...rest }) { - return ( - - Leave - - - ); -} - -export const HeaderButton = forwardRef( - ({ on, className, children, ...rest }, ref) => { - const { buttonProps } = useButton(rest, ref); - return ( - - ); - } -); - -export function HeaderDropdownButton({ children, content }) { - const buttonRef = useRef(); - const [open, setOpen] = useState(false); - - useEffect(() => { - function onClick() { - if (open) { - setOpen(false); - } - } - - window.addEventListener("click", onClick); - - return () => { - window.removeEventListener("click", onClick); - }; - }, [open]); - - return ( -
- - {open && ( -
-
    {content}
-
- )} -
- ); -} - -export function HeaderDropdownItem({ active, children, className, ...rest }) { - return ( -
  • - {children} -
  • - ); -} - -export function UserMenu({ userName, children }) { - return ( - - Profile -
    - - {userName} -
    -
    - ); -} - -export function SettingsButton(props) { - return ( - - Show Dev Tools - - - ); -} - -export function LayoutToggleButton({ layout, setLayout, ...rest }) { - return ( - - setLayout("freedom")}> - - Freedom - {layout === "freedom" && } - - setLayout("spotlight")}> - - Spotlight - {layout === "spotlight" && ( - - )} - - - } - > - Layout Type - {layout === "spotlight" ? : } - - ); -} - -export function ButtonTooltip({ className, children }) { - return ( -
    - {children} -
    - ); -} diff --git a/src/RoomButton.module.css b/src/RoomButton.module.css deleted file mode 100644 index 5181cf3..0000000 --- a/src/RoomButton.module.css +++ /dev/null @@ -1,213 +0,0 @@ -/* -Copyright 2021 New Vector Ltd - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -.roomButton, -.headerButton, -.dropdownButton { - position: relative; - display: flex; - justify-content: center; - align-items: center; - background-color: transparent; - padding: 0; - border: none; - cursor: pointer; -} - -.roomButton { - width: 50px; - height: 50px; - border-radius: 50px; - background-color: #394049; -} - -.roomButton:hover { - background-color: #8d97a5; -} - -.roomButton:active { -} - -.roomButton.on, -.roomButton.off { - background-color: #ffffff; -} - -.headerButton svg * { - fill: #8e99a4; -} - -.headerButton:hover svg * { - fill: #8d97a5; -} - -.headerButton:hover svg * { - fill: #8d97a5; -} - -.headerButton.on svg * { - fill: #0dbd8b; -} - -.hangupButton, -.hangupButton:hover { - background-color: #ff5b55; -} - -.roomButton.on svg * { - fill: #0dbd8b; -} - -.roomButton.off svg * { - fill: #21262c; -} - -.dropdownButtonContainer { - position: relative; -} - -.dropdownButton { - width: 15px; - height: 15px; - border-radius: 15px; - background-color: #394049; - position: absolute; - bottom: 0; - right: 0; - cursor: pointer; -} - -.dropdownButton:hover { - background-color: #8d97a5; -} - -.dropdownButton:hover svg * { - fill: #8d97a5; -} - -.dropdownContainer { - position: absolute; - left: 50%; - transform: translate(0, -100%); - top: -5px; - background-color: var(--bgColor4); - border-radius: 8px; - overflow: hidden; -} - -.headerDropdownContainer { - transform: translate(-100%, 36px); - left: 100%; - z-index: 1; -} - -.checkIcon { - position: absolute; - right: 16px; -} - -.checkIcon * { - stroke: var(--textColor1); -} - -.dropdownContainer ul { - list-style: none; - margin: 0; - padding: 0; -} - -.dropdownContainer li { - position: relative; - padding: 12px; - width: 200px; - cursor: pointer; - font-size: 15px; - display: flex; - align-items: center; -} - -.dropdownContainer li > * { - margin-right: 5px; -} - -.dropdownContainer li > :last-child { - margin-right: 0; -} - -.dropdownContainer li:hover { - background-color: var(--bgColor5); -} - -.dropdownActiveItem { - color: #0dbd8b; -} - -.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; -} - -.roomButton:hover .buttonTooltip { - display: flex; - bottom: calc(100% + 6px); -} - -.headerButton:hover .buttonTooltip { - display: flex; - top: calc(100% + 6px); -} - -.userButton { - display: flex; - align-items: center; - color: var(--textColor1); - font-weight: 600; - font-size: 15px; -} - -.userButton > * { - margin-right: 5px; -} - -.userButton svg * { - fill: var(--textColor1) !important; -} - -.headerButton:hover .userButton > * { - color: var(--textColor1); -} - -.headerButton:hover .userButton svg * { - fill: var(--textColor1); -} - -.userButton > :last-child { - margin-right: 0; -} diff --git a/src/UserMenu.jsx b/src/UserMenu.jsx index b5a5851..ddf7885 100644 --- a/src/UserMenu.jsx +++ b/src/UserMenu.jsx @@ -1,5 +1,5 @@ import React, { useCallback, useMemo } from "react"; -import { ButtonTooltip, HeaderButton } from "./RoomButton"; +import { ButtonTooltip, Button } from "./button"; import { PopoverMenuTrigger } from "./PopoverMenu"; import { ReactComponent as UserIcon } from "./icons/User.svg"; import { ReactComponent as LoginIcon } from "./icons/Login.svg"; @@ -48,13 +48,13 @@ export function UserMenu({ userName, signedIn, onLogin, onLogout }) { }, [signedIn, userName]); return ( - - + + {(props) => ( - + {items.map(({ key, icon: Icon, label }) => ( diff --git a/src/button/Button.jsx b/src/button/Button.jsx new file mode 100644 index 0000000..d7270f2 --- /dev/null +++ b/src/button/Button.jsx @@ -0,0 +1,101 @@ +import React, { forwardRef } from "react"; +import classNames from "classnames"; +import styles from "./Button.module.css"; +import { ReactComponent as MicIcon } from "../icons/Mic.svg"; +import { ReactComponent as MuteMicIcon } from "../icons/MuteMic.svg"; +import { ReactComponent as VideoIcon } from "../icons/Video.svg"; +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"; + +const variantToClassName = { + default: [styles.button], + toolbar: [styles.toolbarButton], + icon: [styles.iconButton], + copy: [styles.copyButton], +}; + +export const Button = forwardRef( + ({ variant = "default", on, off, className, children, ...rest }, ref) => { + const buttonRef = useObjectRef(ref); + const { buttonProps } = useButton(rest, buttonRef); + + // TODO: react-aria's useButton hook prevents form submission via keyboard + // Remove the hack below after this is merged https://github.com/adobe/react-spectrum/pull/904 + let filteredButtonProps = buttonProps; + + if (rest.type === "submit" && !rest.onPress) { + const { onKeyDown, onKeyUp, ...filtered } = buttonProps; + filteredButtonProps = filtered; + } + + return ( + + ); + } +); + +export function ButtonTooltip({ className, children }) { + return ( +
    + {children} +
    + ); +} + +export function MicButton({ muted, ...rest }) { + return ( + + ); +} + +export function VideoButton({ muted, ...rest }) { + return ( + + ); +} + +export function ScreenshareButton({ enabled, className, ...rest }) { + return ( + + ); +} + +export function HangupButton({ className, ...rest }) { + return ( + + ); +} diff --git a/src/button/Button.module.css b/src/button/Button.module.css new file mode 100644 index 0000000..4064921 --- /dev/null +++ b/src/button/Button.module.css @@ -0,0 +1,155 @@ +/* +Copyright 2021 New Vector Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +.button, +.toolbarButton, +.iconButton { + position: relative; + display: flex; + justify-content: center; + align-items: center; + background-color: transparent; + padding: 0; + border: none; + cursor: pointer; +} + +.button { + color: #fff; + background-color: var(--primaryColor); + padding: 7px 15px; + border-radius: 8px; + font-size: 14px; + font-weight: 700; +} + +.toolbarButton { + width: 50px; + height: 50px; + border-radius: 50px; + background-color: #394049; +} + +.toolbarButton:hover { + background-color: #8d97a5; +} + +.toolbarButton.on, +.toolbarButton.off { + background-color: #ffffff; +} + +.iconButton svg * { + fill: #8e99a4; +} + +.iconButton:hover svg * { + fill: #8d97a5; +} + +.iconButton:hover svg * { + fill: #8d97a5; +} + +.iconButton.on svg * { + fill: #0dbd8b; +} + +.hangupButton, +.hangupButton:hover { + background-color: #ff5b55; +} + +.toolbarButton.on svg * { + fill: #0dbd8b; +} + +.toolbarButton.off svg * { + 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); +} + +.copyButton { + position: relative; + display: flex; + justify-content: center; + align-items: center; + background-color: transparent; + cursor: pointer; + border: 2px solid #0dbd8b; + border-radius: 8px; + color: #0dbd8b; + width: 100%; + height: 40px; + transition: border-color 250ms, background-color 250ms; + padding: 0 20px; +} + +.copyButton span { + font-weight: 600; + font-size: 15px; + margin-right: 10px; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + flex: 1; +} + +.copyButton svg { + flex-shrink: 0; +} + +.copyButton:not(.on) svg * { + fill: #0dbd8b; +} + +.copyButton.on { + border-color: transparent; + background-color: #0dbd8b; + color: white; +} + +.copyButton.on svg * { + stroke: white; +} diff --git a/src/button/CopyButton.jsx b/src/button/CopyButton.jsx new file mode 100644 index 0000000..8d9a0fc --- /dev/null +++ b/src/button/CopyButton.jsx @@ -0,0 +1,25 @@ +import React from "react"; +import useClipboard from "react-use-clipboard"; +import { ReactComponent as CheckIcon } from "../icons/Check.svg"; +import { ReactComponent as CopyIcon } from "../icons/Copy.svg"; +import { Button } from "./Button"; + +export function CopyButton({ value, children, ...rest }) { + const [isCopied, setCopied] = useClipboard(value, { successDuration: 3000 }); + + return ( + + ); +} diff --git a/src/button/index.js b/src/button/index.js new file mode 100644 index 0000000..e20a7cb --- /dev/null +++ b/src/button/index.js @@ -0,0 +1,2 @@ +export * from "./Button"; +export * from "./CopyButton";