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…
	
	Add table
		Add a link
		
	
		Reference in a new issue