Refactor buttons
This commit is contained in:
parent
db1fb064ca
commit
4cb144809f
19 changed files with 313 additions and 622 deletions
|
|
@ -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 (
|
||||
<button
|
||||
{...buttonProps}
|
||||
className={classNames(
|
||||
styles.copyButton,
|
||||
{ [styles.copied]: isCopied },
|
||||
className
|
||||
)}
|
||||
>
|
||||
{isCopied ? (
|
||||
<>
|
||||
<span>Copied!</span>
|
||||
<CheckIcon />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<span>{children || value}</span>
|
||||
<CopyIcon />
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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 (
|
||||
<PopoverMenuTrigger placement="bottom right">
|
||||
<HeaderButton>
|
||||
<Button variant="icon">
|
||||
<ButtonTooltip>Layout Type</ButtonTooltip>
|
||||
{layout === "spotlight" ? <SpotlightIcon /> : <FreedomIcon />}
|
||||
</HeaderButton>
|
||||
</Button>
|
||||
{(props) => (
|
||||
<Menu {...props} label="Grid layout menu" onAction={setLayout}>
|
||||
<Item key="freedom" textValue="Freedom">
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<button className={styles.backButton} onClick={onBack}>
|
||||
<button className={styles.backButton} ref={ref} {...buttonProps}>
|
||||
<ArrowLeftIcon width={16} height={16} />
|
||||
<RoomHeaderInfo roomName={roomName} />
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
export function UserDropdownMenu({ userName, signedIn, onLogout }) {
|
||||
if (!signedIn) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<UserMenu userName={userName}>
|
||||
<HeaderDropdownItem onClick={onLogout} className={styles.signOutButton}>
|
||||
Sign Out
|
||||
</HeaderDropdownItem>
|
||||
</UserMenu>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 });
|
||||
|
||||
|
|
|
|||
|
|
@ -44,18 +44,6 @@ export const InputField = forwardRef(
|
|||
}
|
||||
);
|
||||
|
||||
export const Button = forwardRef(({ className, children, ...rest }, ref) => {
|
||||
return (
|
||||
<button
|
||||
className={classNames(styles.button, className)}
|
||||
ref={ref}
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
});
|
||||
|
||||
export function ErrorMessage({ children }) {
|
||||
return <p className={styles.errorMessage}>{children}</p>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<>
|
||||
<PopoverMenuTrigger>
|
||||
<RoomButton>
|
||||
<Button variant="toolbar">
|
||||
<ButtonTooltip>More</ButtonTooltip>
|
||||
<OverflowIcon />
|
||||
</RoomButton>
|
||||
</Button>
|
||||
{(props) => (
|
||||
<Menu {...props} label="More menu" onAction={onAction}>
|
||||
<Item key="invite" textValue="Invite people">
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
10
src/Room.jsx
10
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({
|
|||
<Header>
|
||||
<LeftNav>
|
||||
<RoomSetupHeaderInfo
|
||||
onBack={() => history.goBack()}
|
||||
onPress={() => history.goBack()}
|
||||
roomName={roomName}
|
||||
/>
|
||||
</LeftNav>
|
||||
|
|
@ -310,7 +310,7 @@ function RoomSetupView({
|
|||
<Button
|
||||
className={styles.joinCallButton}
|
||||
disabled={state !== GroupCallState.LocalCallFeedInitialized}
|
||||
onClick={onEnter}
|
||||
onPress={onEnter}
|
||||
>
|
||||
Join call now
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<button
|
||||
className={classNames(styles.roomButton, className, {
|
||||
[styles.on]: on,
|
||||
[styles.off]: off,
|
||||
})}
|
||||
{...buttonProps}
|
||||
ref={ref}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
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 (
|
||||
<div className={styles.dropdownButtonContainer}>
|
||||
{children}
|
||||
<button
|
||||
ref={buttonRef}
|
||||
className={styles.dropdownButton}
|
||||
onClick={() => setOpen(true)}
|
||||
>
|
||||
<ChevronIcon />
|
||||
</button>
|
||||
{open && (
|
||||
<div className={styles.dropdownContainer}>
|
||||
<ul>
|
||||
{options.map((item) => (
|
||||
<li
|
||||
key={item.value}
|
||||
className={classNames({
|
||||
[styles.dropdownActiveItem]: item.value === value,
|
||||
})}
|
||||
onClick={() => onChange(item)}
|
||||
>
|
||||
{item.label}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function MicButton({ muted, ...rest }) {
|
||||
return (
|
||||
<RoomButton {...rest} off={muted}>
|
||||
<ButtonTooltip>
|
||||
{muted ? "Unmute microphone" : "Mute microphone"}
|
||||
</ButtonTooltip>
|
||||
{muted ? <MuteMicIcon /> : <MicIcon />}
|
||||
</RoomButton>
|
||||
);
|
||||
}
|
||||
|
||||
export function VideoButton({ muted, ...rest }) {
|
||||
return (
|
||||
<RoomButton {...rest} off={muted}>
|
||||
<ButtonTooltip>
|
||||
{muted ? "Turn on camera" : "Turn off camera"}
|
||||
</ButtonTooltip>
|
||||
{muted ? <DisableVideoIcon /> : <VideoIcon />}
|
||||
</RoomButton>
|
||||
);
|
||||
}
|
||||
|
||||
export function ScreenshareButton({ enabled, className, ...rest }) {
|
||||
return (
|
||||
<RoomButton {...rest} on={enabled}>
|
||||
<ButtonTooltip>
|
||||
{enabled ? "Stop sharing screen" : "Share screen"}
|
||||
</ButtonTooltip>
|
||||
<ScreenshareIcon />
|
||||
</RoomButton>
|
||||
);
|
||||
}
|
||||
|
||||
export function HangupButton({ className, ...rest }) {
|
||||
return (
|
||||
<RoomButton
|
||||
className={classNames(styles.hangupButton, className)}
|
||||
{...rest}
|
||||
>
|
||||
<ButtonTooltip>Leave</ButtonTooltip>
|
||||
<HangupIcon />
|
||||
</RoomButton>
|
||||
);
|
||||
}
|
||||
|
||||
export const HeaderButton = forwardRef(
|
||||
({ on, className, children, ...rest }, ref) => {
|
||||
const { buttonProps } = useButton(rest, ref);
|
||||
return (
|
||||
<button
|
||||
className={classNames(styles.headerButton, className, {
|
||||
[styles.on]: on,
|
||||
})}
|
||||
{...buttonProps}
|
||||
ref={ref}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
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 (
|
||||
<div className={styles.dropdownButtonContainer}>
|
||||
<button
|
||||
ref={buttonRef}
|
||||
className={classNames(styles.headerButton, { [styles.on]: open })}
|
||||
onClick={() => setOpen(true)}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
{open && (
|
||||
<div
|
||||
className={classNames(
|
||||
styles.dropdownContainer,
|
||||
styles.headerDropdownContainer
|
||||
)}
|
||||
>
|
||||
<ul>{content}</ul>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function HeaderDropdownItem({ active, children, className, ...rest }) {
|
||||
return (
|
||||
<li
|
||||
className={classNames(className, {
|
||||
[styles.dropdownActiveItem]: active,
|
||||
})}
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
export function UserMenu({ userName, children }) {
|
||||
return (
|
||||
<HeaderDropdownButton content={children}>
|
||||
<ButtonTooltip>Profile</ButtonTooltip>
|
||||
<div className={styles.userButton}>
|
||||
<UserIcon />
|
||||
<span>{userName}</span>
|
||||
</div>
|
||||
</HeaderDropdownButton>
|
||||
);
|
||||
}
|
||||
|
||||
export function SettingsButton(props) {
|
||||
return (
|
||||
<HeaderButton {...props}>
|
||||
<ButtonTooltip>Show Dev Tools</ButtonTooltip>
|
||||
<SettingsIcon width={20} height={20} />
|
||||
</HeaderButton>
|
||||
);
|
||||
}
|
||||
|
||||
export function LayoutToggleButton({ layout, setLayout, ...rest }) {
|
||||
return (
|
||||
<HeaderDropdownButton
|
||||
{...rest}
|
||||
content={
|
||||
<>
|
||||
<HeaderDropdownItem onClick={() => setLayout("freedom")}>
|
||||
<FreedomIcon />
|
||||
<span>Freedom</span>
|
||||
{layout === "freedom" && <CheckIcon className={styles.checkIcon} />}
|
||||
</HeaderDropdownItem>
|
||||
<HeaderDropdownItem onClick={() => setLayout("spotlight")}>
|
||||
<SpotlightIcon />
|
||||
<span>Spotlight</span>
|
||||
{layout === "spotlight" && (
|
||||
<CheckIcon className={styles.checkIcon} />
|
||||
)}
|
||||
</HeaderDropdownItem>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<ButtonTooltip>Layout Type</ButtonTooltip>
|
||||
{layout === "spotlight" ? <SpotlightIcon /> : <FreedomIcon />}
|
||||
</HeaderDropdownButton>
|
||||
);
|
||||
}
|
||||
|
||||
export function ButtonTooltip({ className, children }) {
|
||||
return (
|
||||
<div className={classNames(styles.buttonTooltip, className)}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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 (
|
||||
<PopoverMenuTrigger onAction={onAction} placement="bottom right">
|
||||
<HeaderButton className={styles.userButton}>
|
||||
<PopoverMenuTrigger placement="bottom right">
|
||||
<Button variant="icon" className={styles.userButton}>
|
||||
<ButtonTooltip>Profile</ButtonTooltip>
|
||||
<UserIcon />
|
||||
</HeaderButton>
|
||||
</Button>
|
||||
{(props) => (
|
||||
<Menu {...props} label="User menu">
|
||||
<Menu {...props} label="User menu" onAction={onAction}>
|
||||
{items.map(({ key, icon: Icon, label }) => (
|
||||
<Item key={key} textValue={label}>
|
||||
<Icon />
|
||||
|
|
|
|||
101
src/button/Button.jsx
Normal file
101
src/button/Button.jsx
Normal file
|
|
@ -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 (
|
||||
<button
|
||||
className={classNames(variantToClassName[variant], className, {
|
||||
[styles.on]: on,
|
||||
[styles.off]: off,
|
||||
})}
|
||||
{...filteredButtonProps}
|
||||
ref={buttonRef}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export function ButtonTooltip({ className, children }) {
|
||||
return (
|
||||
<div className={classNames(styles.buttonTooltip, className)}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function MicButton({ muted, ...rest }) {
|
||||
return (
|
||||
<Button variant="toolbar" {...rest} off={muted}>
|
||||
<ButtonTooltip>
|
||||
{muted ? "Unmute microphone" : "Mute microphone"}
|
||||
</ButtonTooltip>
|
||||
{muted ? <MuteMicIcon /> : <MicIcon />}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
export function VideoButton({ muted, ...rest }) {
|
||||
return (
|
||||
<Button variant="toolbar" {...rest} off={muted}>
|
||||
<ButtonTooltip>
|
||||
{muted ? "Turn on camera" : "Turn off camera"}
|
||||
</ButtonTooltip>
|
||||
{muted ? <DisableVideoIcon /> : <VideoIcon />}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
export function ScreenshareButton({ enabled, className, ...rest }) {
|
||||
return (
|
||||
<Button variant="toolbar" {...rest} on={enabled}>
|
||||
<ButtonTooltip>
|
||||
{enabled ? "Stop sharing screen" : "Share screen"}
|
||||
</ButtonTooltip>
|
||||
<ScreenshareIcon />
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
export function HangupButton({ className, ...rest }) {
|
||||
return (
|
||||
<Button
|
||||
variant="toolbar"
|
||||
className={classNames(styles.hangupButton, className)}
|
||||
{...rest}
|
||||
>
|
||||
<ButtonTooltip>Leave</ButtonTooltip>
|
||||
<HangupIcon />
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
155
src/button/Button.module.css
Normal file
155
src/button/Button.module.css
Normal file
|
|
@ -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;
|
||||
}
|
||||
25
src/button/CopyButton.jsx
Normal file
25
src/button/CopyButton.jsx
Normal file
|
|
@ -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 (
|
||||
<Button {...rest} variant="copy" on={isCopied} onPress={setCopied}>
|
||||
{isCopied ? (
|
||||
<>
|
||||
<span>Copied!</span>
|
||||
<CheckIcon />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<span>{children || value}</span>
|
||||
<CopyIcon />
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
2
src/button/index.js
Normal file
2
src/button/index.js
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
export * from "./Button";
|
||||
export * from "./CopyButton";
|
||||
Loading…
Add table
Add a link
Reference in a new issue