Merge pull request #356 from robintown/call-type-dropdown
Add a dropdown to choose between video calls and radio calls
This commit is contained in:
commit
5c4bab2a8a
11 changed files with 133 additions and 58 deletions
|
@ -39,3 +39,12 @@
|
|||
border-bottom-left-radius: 8px;
|
||||
border-bottom-right-radius: 8px;
|
||||
}
|
||||
|
||||
.checkIcon {
|
||||
position: absolute;
|
||||
right: 16px;
|
||||
}
|
||||
|
||||
.checkIcon * {
|
||||
stroke: var(--textColor1);
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ import { ReactComponent as HangupIcon } from "../icons/Hangup.svg";
|
|||
import { ReactComponent as ScreenshareIcon } from "../icons/Screenshare.svg";
|
||||
import { ReactComponent as SettingsIcon } from "../icons/Settings.svg";
|
||||
import { ReactComponent as AddUserIcon } from "../icons/AddUser.svg";
|
||||
import { ReactComponent as ArrowDownIcon } from "../icons/ArrowDown.svg";
|
||||
import { useButton } from "@react-aria/button";
|
||||
import { mergeProps, useObjectRef } from "@react-aria/utils";
|
||||
import { TooltipTrigger } from "../Tooltip";
|
||||
|
@ -36,9 +37,11 @@ export const variantToClassName = {
|
|||
icon: [styles.iconButton],
|
||||
secondary: [styles.secondary],
|
||||
copy: [styles.copyButton],
|
||||
secondaryCopy: [styles.secondaryCopy],
|
||||
iconCopy: [styles.iconCopyButton],
|
||||
secondaryCopy: [styles.copyButton],
|
||||
secondaryHangup: [styles.secondaryHangup],
|
||||
dropdown: [styles.dropdownButton],
|
||||
};
|
||||
|
||||
export const sizeToClassName = {
|
||||
|
@ -86,13 +89,13 @@ export const Button = forwardRef(
|
|||
{
|
||||
[styles.on]: on,
|
||||
[styles.off]: off,
|
||||
[styles.secondaryCopy]: variant === "secondaryCopy",
|
||||
}
|
||||
)}
|
||||
{...mergeProps(rest, filteredButtonProps)}
|
||||
ref={buttonRef}
|
||||
>
|
||||
{children}
|
||||
{variant === "dropdown" && <ArrowDownIcon />}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -21,7 +21,8 @@ limitations under the License.
|
|||
.iconCopyButton,
|
||||
.secondary,
|
||||
.secondaryHangup,
|
||||
.copyButton {
|
||||
.copyButton,
|
||||
.dropdownButton {
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
@ -184,6 +185,25 @@ limitations under the License.
|
|||
stroke: #0dbd8b;
|
||||
}
|
||||
|
||||
.dropdownButton {
|
||||
color: var(--textColor1);
|
||||
padding: 2px 8px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.dropdownButton:hover,
|
||||
.dropdownButton.on {
|
||||
background-color: var(--bgColor4);
|
||||
}
|
||||
|
||||
.dropdownButton svg {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.dropdownButton svg * {
|
||||
fill: var(--textColor1);
|
||||
}
|
||||
|
||||
.lg {
|
||||
height: 40px;
|
||||
}
|
||||
|
|
3
src/home/CallTypeDropdown.module.css
Normal file
3
src/home/CallTypeDropdown.module.css
Normal file
|
@ -0,0 +1,3 @@
|
|||
.label {
|
||||
margin-bottom: 0;
|
||||
}
|
69
src/home/CallTypeDropdown.tsx
Normal file
69
src/home/CallTypeDropdown.tsx
Normal file
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
Copyright 2022 Matrix.org Foundation C.I.C.
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
import React, { FC } from "react";
|
||||
import { Item } from "@react-stately/collections";
|
||||
|
||||
import { Headline } from "../typography/Typography";
|
||||
import { Button } from "../button";
|
||||
import { PopoverMenuTrigger } from "../popover/PopoverMenu";
|
||||
import { ReactComponent as VideoIcon } from "../icons/Video.svg";
|
||||
import { ReactComponent as MicIcon } from "../icons/Mic.svg";
|
||||
import { ReactComponent as CheckIcon } from "../icons/Check.svg";
|
||||
import styles from "./CallTypeDropdown.module.css";
|
||||
import commonStyles from "./common.module.css";
|
||||
import menuStyles from "../Menu.module.css";
|
||||
import { Menu } from "../Menu";
|
||||
|
||||
export enum CallType {
|
||||
Video = "video",
|
||||
Radio = "radio",
|
||||
}
|
||||
|
||||
interface Props {
|
||||
callType: CallType;
|
||||
setCallType: (value: CallType) => void;
|
||||
}
|
||||
|
||||
export const CallTypeDropdown: FC<Props> = ({ callType, setCallType }) => {
|
||||
return (
|
||||
<PopoverMenuTrigger placement="bottom">
|
||||
<Button variant="dropdown" className={commonStyles.headline}>
|
||||
<Headline className={styles.label}>
|
||||
{callType === CallType.Video ? "Video call" : "Radio call"}
|
||||
</Headline>
|
||||
</Button>
|
||||
{(props) => (
|
||||
<Menu {...props} label="Call type menu" onAction={setCallType}>
|
||||
<Item key={CallType.Video} textValue="Video call">
|
||||
<VideoIcon />
|
||||
<span>Video call</span>
|
||||
{callType === CallType.Video && (
|
||||
<CheckIcon className={menuStyles.checkIcon} />
|
||||
)}
|
||||
</Item>
|
||||
<Item key={CallType.Radio} textValue="Radio call">
|
||||
<MicIcon />
|
||||
<span>Radio call</span>
|
||||
{callType === CallType.Radio && (
|
||||
<CheckIcon className={menuStyles.checkIcon} />
|
||||
)}
|
||||
</Item>
|
||||
</Menu>
|
||||
)}
|
||||
</PopoverMenuTrigger>
|
||||
);
|
||||
};
|
|
@ -27,21 +27,21 @@ import { UserMenuContainer } from "../UserMenuContainer";
|
|||
import { useModalTriggerState } from "../Modal";
|
||||
import { JoinExistingCallModal } from "./JoinExistingCallModal";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import { Headline, Title } from "../typography/Typography";
|
||||
import { Title } from "../typography/Typography";
|
||||
import { Form } from "../form/Form";
|
||||
import { useShouldShowPtt } from "../useShouldShowPtt";
|
||||
import { CallType, CallTypeDropdown } from "./CallTypeDropdown";
|
||||
|
||||
export function RegisteredView({ client }) {
|
||||
const [callType, setCallType] = useState(CallType.Video);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState();
|
||||
const history = useHistory();
|
||||
const shouldShowPtt = useShouldShowPtt();
|
||||
const onSubmit = useCallback(
|
||||
(e) => {
|
||||
e.preventDefault();
|
||||
const data = new FormData(e.target);
|
||||
const roomName = data.get("callName");
|
||||
const ptt = data.get("ptt") !== null;
|
||||
const ptt = callType === CallType.Radio;
|
||||
|
||||
async function submit() {
|
||||
setError(undefined);
|
||||
|
@ -68,7 +68,7 @@ export function RegisteredView({ client }) {
|
|||
}
|
||||
});
|
||||
},
|
||||
[client]
|
||||
[client, callType]
|
||||
);
|
||||
|
||||
const recentRooms = useGroupCallRooms(client);
|
||||
|
@ -79,6 +79,9 @@ export function RegisteredView({ client }) {
|
|||
history.push(`/${existingRoomId}`);
|
||||
}, [history, existingRoomId]);
|
||||
|
||||
const callNameLabel =
|
||||
callType === CallType.Video ? "Video call name" : "Radio call name";
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header>
|
||||
|
@ -92,16 +95,14 @@ export function RegisteredView({ client }) {
|
|||
<div className={commonStyles.container}>
|
||||
<main className={commonStyles.main}>
|
||||
<HeaderLogo className={commonStyles.logo} />
|
||||
<Headline className={commonStyles.headline}>
|
||||
Enter a call name
|
||||
</Headline>
|
||||
<CallTypeDropdown callType={callType} setCallType={setCallType} />
|
||||
<Form className={styles.form} onSubmit={onSubmit}>
|
||||
<FieldRow className={styles.fieldRow}>
|
||||
<InputField
|
||||
id="callName"
|
||||
name="callName"
|
||||
label="Call name"
|
||||
placeholder="Call name"
|
||||
label={callNameLabel}
|
||||
placeholder={callNameLabel}
|
||||
type="text"
|
||||
required
|
||||
autoComplete="off"
|
||||
|
@ -116,16 +117,6 @@ export function RegisteredView({ client }) {
|
|||
{loading ? "Loading..." : "Go"}
|
||||
</Button>
|
||||
</FieldRow>
|
||||
{shouldShowPtt && (
|
||||
<FieldRow className={styles.fieldRow}>
|
||||
<InputField
|
||||
id="ptt"
|
||||
name="ptt"
|
||||
label="Push to Talk"
|
||||
type="checkbox"
|
||||
/>
|
||||
</FieldRow>
|
||||
)}
|
||||
{error && (
|
||||
<FieldRow className={styles.fieldRow}>
|
||||
<ErrorMessage>{error.message}</ErrorMessage>
|
||||
|
|
|
@ -29,14 +29,14 @@ import { JoinExistingCallModal } from "./JoinExistingCallModal";
|
|||
import { useRecaptcha } from "../auth/useRecaptcha";
|
||||
import { Body, Caption, Link, Headline } from "../typography/Typography";
|
||||
import { Form } from "../form/Form";
|
||||
import { CallType, CallTypeDropdown } from "./CallTypeDropdown";
|
||||
import styles from "./UnauthenticatedView.module.css";
|
||||
import commonStyles from "./common.module.css";
|
||||
import { generateRandomName } from "../auth/generateRandomName";
|
||||
import { useShouldShowPtt } from "../useShouldShowPtt";
|
||||
|
||||
export function UnauthenticatedView() {
|
||||
const { setClient } = useClient();
|
||||
const shouldShowPtt = useShouldShowPtt();
|
||||
const [callType, setCallType] = useState(CallType.Video);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState();
|
||||
const [{ privacyPolicyUrl, recaptchaKey }, register] =
|
||||
|
@ -53,7 +53,7 @@ export function UnauthenticatedView() {
|
|||
const data = new FormData(e.target);
|
||||
const roomName = data.get("callName");
|
||||
const displayName = data.get("displayName");
|
||||
const ptt = data.get("ptt") !== null;
|
||||
const ptt = callType === CallType.Radio;
|
||||
|
||||
async function submit() {
|
||||
setError(undefined);
|
||||
|
@ -100,9 +100,12 @@ export function UnauthenticatedView() {
|
|||
reset();
|
||||
});
|
||||
},
|
||||
[register, reset, execute, history]
|
||||
[register, reset, execute, history, callType]
|
||||
);
|
||||
|
||||
const callNameLabel =
|
||||
callType === CallType.Video ? "Video call name" : "Radio call name";
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header>
|
||||
|
@ -116,16 +119,14 @@ export function UnauthenticatedView() {
|
|||
<div className={commonStyles.container}>
|
||||
<main className={commonStyles.main}>
|
||||
<HeaderLogo className={commonStyles.logo} />
|
||||
<Headline className={commonStyles.headline}>
|
||||
Enter a call name
|
||||
</Headline>
|
||||
<CallTypeDropdown callType={callType} setCallType={setCallType} />
|
||||
<Form className={styles.form} onSubmit={onSubmit}>
|
||||
<FieldRow>
|
||||
<InputField
|
||||
id="callName"
|
||||
name="callName"
|
||||
label="Call name"
|
||||
placeholder="Call name"
|
||||
label={callNameLabel}
|
||||
placeholder={callNameLabel}
|
||||
type="text"
|
||||
required
|
||||
autoComplete="off"
|
||||
|
@ -142,16 +143,6 @@ export function UnauthenticatedView() {
|
|||
autoComplete="off"
|
||||
/>
|
||||
</FieldRow>
|
||||
{shouldShowPtt && (
|
||||
<FieldRow>
|
||||
<InputField
|
||||
id="ptt"
|
||||
name="ptt"
|
||||
label="Push to Talk"
|
||||
type="checkbox"
|
||||
/>
|
||||
</FieldRow>
|
||||
)}
|
||||
<Caption>
|
||||
By clicking "Go", you agree to our{" "}
|
||||
<Link href={privacyPolicyUrl}>Terms and conditions</Link>
|
||||
|
|
|
@ -123,6 +123,7 @@ limitations under the License.
|
|||
body {
|
||||
background-color: var(--bgColor1);
|
||||
color: var(--textColor1);
|
||||
color-scheme: dark;
|
||||
margin: 0;
|
||||
font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto",
|
||||
"Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
|
||||
|
|
|
@ -20,7 +20,7 @@ import { PopoverMenuTrigger } from "../popover/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";
|
||||
import menuStyles from "../Menu.module.css";
|
||||
import { Menu } from "../Menu";
|
||||
import { Item } from "@react-stately/collections";
|
||||
import { Tooltip, TooltipTrigger } from "../Tooltip";
|
||||
|
@ -39,13 +39,15 @@ export function GridLayoutMenu({ layout, setLayout }) {
|
|||
<Item key="freedom" textValue="Freedom">
|
||||
<FreedomIcon />
|
||||
<span>Freedom</span>
|
||||
{layout === "freedom" && <CheckIcon className={styles.checkIcon} />}
|
||||
{layout === "freedom" && (
|
||||
<CheckIcon className={menuStyles.checkIcon} />
|
||||
)}
|
||||
</Item>
|
||||
<Item key="spotlight" textValue="Spotlight">
|
||||
<SpotlightIcon />
|
||||
<span>Spotlight</span>
|
||||
{layout === "spotlight" && (
|
||||
<CheckIcon className={styles.checkIcon} />
|
||||
<CheckIcon className={menuStyles.checkIcon} />
|
||||
)}
|
||||
</Item>
|
||||
</Menu>
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
.checkIcon {
|
||||
position: absolute;
|
||||
right: 16px;
|
||||
}
|
||||
|
||||
.checkIcon * {
|
||||
stroke: var(--textColor1);
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
import { useLocation } from "react-router-dom";
|
||||
|
||||
export function useShouldShowPtt() {
|
||||
const { hash } = useLocation();
|
||||
return hash.startsWith("#ptt");
|
||||
}
|
Loading…
Reference in a new issue