Additional in-room PTT styling
This commit is contained in:
parent
c430ebb3a3
commit
b6c926d2c8
5 changed files with 139 additions and 50 deletions
|
|
@ -4,35 +4,55 @@ import classNames from "classnames";
|
||||||
import { Avatar } from "./Avatar";
|
import { Avatar } from "./Avatar";
|
||||||
import { getAvatarUrl } from "./matrix-utils";
|
import { getAvatarUrl } from "./matrix-utils";
|
||||||
|
|
||||||
export function Facepile({ className, client, participants, ...rest }) {
|
export function Facepile({
|
||||||
|
className,
|
||||||
|
client,
|
||||||
|
participants,
|
||||||
|
max,
|
||||||
|
size,
|
||||||
|
...rest
|
||||||
|
}) {
|
||||||
|
const _size = size === "md" ? 36 : 24;
|
||||||
|
const _overlap = size === "md" ? 8 : 2;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={classNames(styles.facepile, className)}
|
className={classNames(
|
||||||
|
styles.facepile,
|
||||||
|
{ [styles.md]: size === "md" },
|
||||||
|
className
|
||||||
|
)}
|
||||||
title={participants.map((member) => member.name).join(", ")}
|
title={participants.map((member) => member.name).join(", ")}
|
||||||
|
style={{ width: participants.length * _size + _overlap }}
|
||||||
{...rest}
|
{...rest}
|
||||||
>
|
>
|
||||||
{participants.slice(0, 3).map((member, i) => {
|
{participants.slice(0, max).map((member, i) => {
|
||||||
const avatarUrl = member.user?.avatarUrl;
|
const avatarUrl = member.user?.avatarUrl;
|
||||||
return (
|
return (
|
||||||
<Avatar
|
<Avatar
|
||||||
key={member.userId}
|
key={member.userId}
|
||||||
size="xs"
|
size={size}
|
||||||
src={avatarUrl && getAvatarUrl(client, avatarUrl, 22)}
|
src={avatarUrl && getAvatarUrl(client, avatarUrl, _size)}
|
||||||
fallback={member.name.slice(0, 1).toUpperCase()}
|
fallback={member.name.slice(0, 1).toUpperCase()}
|
||||||
className={styles.avatar}
|
className={styles.avatar}
|
||||||
style={{ left: i * 22 }}
|
style={{ left: i * (_size - _overlap) }}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
{participants.length > 3 && (
|
{participants.length > max && (
|
||||||
<Avatar
|
<Avatar
|
||||||
key="additional"
|
key="additional"
|
||||||
size="xs"
|
size={size}
|
||||||
fallback={`+${participants.length - 3}`}
|
fallback={`+${participants.length - max}`}
|
||||||
className={styles.avatar}
|
className={styles.avatar}
|
||||||
style={{ left: 3 * 22 }}
|
style={{ left: max * (_size - _overlap) }}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Facepile.defaultProps = {
|
||||||
|
max: 3,
|
||||||
|
size: "xs",
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,16 @@
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.facepile.md {
|
||||||
|
height: 36px;
|
||||||
|
}
|
||||||
|
|
||||||
.facepile .avatar {
|
.facepile .avatar {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
border: 1px solid var(--bgColor2);
|
border: 1px solid var(--bgColor2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.facepile.md .avatar {
|
||||||
|
border: 2px solid var(--bgColor2);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@ import { ReactComponent as VideoIcon } from "../icons/Video.svg";
|
||||||
import { ReactComponent as DisableVideoIcon } from "../icons/DisableVideo.svg";
|
import { ReactComponent as DisableVideoIcon } from "../icons/DisableVideo.svg";
|
||||||
import { ReactComponent as HangupIcon } from "../icons/Hangup.svg";
|
import { ReactComponent as HangupIcon } from "../icons/Hangup.svg";
|
||||||
import { ReactComponent as ScreenshareIcon } from "../icons/Screenshare.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 { useButton } from "@react-aria/button";
|
import { useButton } from "@react-aria/button";
|
||||||
import { mergeProps, useObjectRef } from "@react-aria/utils";
|
import { mergeProps, useObjectRef } from "@react-aria/utils";
|
||||||
import { TooltipTrigger } from "../Tooltip";
|
import { TooltipTrigger } from "../Tooltip";
|
||||||
|
|
@ -127,3 +129,25 @@ export function HangupButton({ className, ...rest }) {
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function SettingsButton({ className, ...rest }) {
|
||||||
|
return (
|
||||||
|
<TooltipTrigger>
|
||||||
|
<Button variant="toolbar" {...rest}>
|
||||||
|
<SettingsIcon />
|
||||||
|
</Button>
|
||||||
|
{() => "Settings"}
|
||||||
|
</TooltipTrigger>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function InviteButton({ className, ...rest }) {
|
||||||
|
return (
|
||||||
|
<TooltipTrigger>
|
||||||
|
<Button variant="toolbar" {...rest}>
|
||||||
|
<AddUserIcon />
|
||||||
|
</Button>
|
||||||
|
{() => "Invite"}
|
||||||
|
</TooltipTrigger>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import React from "react";
|
||||||
import { useModalTriggerState } from "../Modal";
|
import { useModalTriggerState } from "../Modal";
|
||||||
import { SettingsModal } from "../settings/SettingsModal";
|
import { SettingsModal } from "../settings/SettingsModal";
|
||||||
import { InviteModal } from "./InviteModal";
|
import { InviteModal } from "./InviteModal";
|
||||||
import { Button } from "../button";
|
import { Button, HangupButton, InviteButton, SettingsButton } from "../button";
|
||||||
import { Header, LeftNav, RightNav, RoomSetupHeaderInfo } from "../Header";
|
import { Header, LeftNav, RightNav, RoomSetupHeaderInfo } from "../Header";
|
||||||
import { ReactComponent as AddUserIcon } from "../icons/AddUser.svg";
|
import { ReactComponent as AddUserIcon } from "../icons/AddUser.svg";
|
||||||
import { ReactComponent as SettingsIcon } from "../icons/Settings.svg";
|
import { ReactComponent as SettingsIcon } from "../icons/Settings.svg";
|
||||||
|
|
@ -38,39 +38,45 @@ export function PTTCallView({
|
||||||
<LeftNav>
|
<LeftNav>
|
||||||
<RoomSetupHeaderInfo roomName={roomName} onPress={onLeave} />
|
<RoomSetupHeaderInfo roomName={roomName} onPress={onLeave} />
|
||||||
</LeftNav>
|
</LeftNav>
|
||||||
<RightNav>
|
<RightNav />
|
||||||
<Button variant="secondaryHangup" onPress={onLeave}>
|
|
||||||
Leave
|
|
||||||
</Button>
|
|
||||||
<Button variant="icon" onPress={() => inviteModalState.open()}>
|
|
||||||
<AddUserIcon />
|
|
||||||
</Button>
|
|
||||||
<Button variant="icon" onPress={() => settingsModalState.open()}>
|
|
||||||
<SettingsIcon />
|
|
||||||
</Button>
|
|
||||||
</RightNav>
|
|
||||||
</Header>
|
</Header>
|
||||||
<div className={styles.headerSeparator} />
|
|
||||||
<div className={styles.participants}>
|
|
||||||
<p>{`${participants.length} user${
|
|
||||||
participants.length > 1 ? "s" : ""
|
|
||||||
} connected`}</p>
|
|
||||||
<Facepile client={client} participants={participants} />
|
|
||||||
</div>
|
|
||||||
<div className={styles.center}>
|
<div className={styles.center}>
|
||||||
<PTTButton
|
<div className={styles.participants}>
|
||||||
client={client}
|
<p>{`${participants.length} ${
|
||||||
activeSpeaker={activeSpeaker}
|
participants.length > 1 ? "people" : "person"
|
||||||
groupCall={groupCall}
|
} connected`}</p>
|
||||||
/>
|
<Facepile
|
||||||
<p className={styles.actionTip}>Press and hold spacebar to talk</p>
|
size="md"
|
||||||
{userMediaFeeds.map((callFeed) => (
|
max={8}
|
||||||
<PTTFeed
|
className={styles.facepile}
|
||||||
key={callFeed.userId}
|
client={client}
|
||||||
callFeed={callFeed}
|
participants={participants}
|
||||||
audioOutputDevice={audioOutput}
|
|
||||||
/>
|
/>
|
||||||
))}
|
</div>
|
||||||
|
<div className={styles.talkingInfo}>
|
||||||
|
<h2>Talking...</h2>
|
||||||
|
<p>00:01:24</p>
|
||||||
|
</div>
|
||||||
|
<div className={styles.pttButtonContainer}>
|
||||||
|
<PTTButton
|
||||||
|
client={client}
|
||||||
|
activeSpeaker={activeSpeaker}
|
||||||
|
groupCall={groupCall}
|
||||||
|
/>
|
||||||
|
<p className={styles.actionTip}>Press and hold spacebar to talk</p>
|
||||||
|
{userMediaFeeds.map((callFeed) => (
|
||||||
|
<PTTFeed
|
||||||
|
key={callFeed.userId}
|
||||||
|
callFeed={callFeed}
|
||||||
|
audioOutputDevice={audioOutput}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className={styles.footer}>
|
||||||
|
<SettingsButton onPress={() => settingsModalState.open()} />
|
||||||
|
<HangupButton onPress={onLeave} />
|
||||||
|
<InviteButton onPress={() => inviteModalState.open()} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{settingsModalState.isOpen && (
|
{settingsModalState.isOpen && (
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
min-height: 100%;
|
min-height: 100%;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
|
|
@ -9,32 +10,62 @@
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.headerSeparator {
|
|
||||||
width: calc(100% - 40px);
|
|
||||||
height: 1px;
|
|
||||||
margin: 0 20px;
|
|
||||||
background-color: #21262C;
|
|
||||||
}
|
|
||||||
|
|
||||||
.center {
|
.center {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
margin-top: 48px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.actionTip {
|
.actionTip {
|
||||||
margin-top: 42px;
|
margin-top: 42px;
|
||||||
|
margin-bottom: 45px;
|
||||||
font-size: 17px;
|
font-size: 17px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.participants {
|
.participants {
|
||||||
margin: 24px 20px 0 20px;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin: 20px;
|
||||||
|
margin-bottom: 67px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.participants > p {
|
.participants > p {
|
||||||
color: #A9B2BC;
|
color: #A9B2BC;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.facepile {
|
||||||
|
align-self: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.talkingInfo {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 38px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pttButtonContainer {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 64px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer > * {
|
||||||
|
margin-right: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer > :last-child {
|
||||||
|
margin-right: 0px;
|
||||||
}
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue