Home page styling
This commit is contained in:
parent
9c7006f239
commit
20350e66a2
13 changed files with 467 additions and 216 deletions
58
src/Avatar.jsx
Normal file
58
src/Avatar.jsx
Normal file
|
@ -0,0 +1,58 @@
|
|||
import React, { useMemo } from "react";
|
||||
import classNames from "classnames";
|
||||
import styles from "./Avatar.module.css";
|
||||
|
||||
const backgroundColors = [
|
||||
"#5C56F5",
|
||||
"#03B381",
|
||||
"#368BD6",
|
||||
"#AC3BA8",
|
||||
"#E64F7A",
|
||||
"#FF812D",
|
||||
"#2DC2C5",
|
||||
"#74D12C",
|
||||
];
|
||||
|
||||
function hashStringToArrIndex(str, arrLength) {
|
||||
let sum = 0;
|
||||
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
sum += str.charCodeAt(i);
|
||||
}
|
||||
|
||||
return sum % arrLength;
|
||||
}
|
||||
|
||||
export function Avatar({
|
||||
bgKey,
|
||||
src,
|
||||
fallback,
|
||||
size,
|
||||
className,
|
||||
style,
|
||||
...rest
|
||||
}) {
|
||||
const backgroundColor = useMemo(() => {
|
||||
const index = hashStringToArrIndex(
|
||||
bgKey || fallback || src,
|
||||
backgroundColors.length
|
||||
);
|
||||
return backgroundColors[index];
|
||||
}, [bgKey, src, fallback]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames(styles.avatar, styles[size || "md"], className)}
|
||||
style={{ backgroundColor, ...style }}
|
||||
{...rest}
|
||||
>
|
||||
{src ? (
|
||||
<img src={src} />
|
||||
) : typeof fallback === "string" ? (
|
||||
<span>{fallback}</span>
|
||||
) : (
|
||||
fallback
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
46
src/Avatar.module.css
Normal file
46
src/Avatar.module.css
Normal file
|
@ -0,0 +1,46 @@
|
|||
.avatar {
|
||||
position: relative;
|
||||
color: #ffffff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
pointer-events: none;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.avatar img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.avatar svg * {
|
||||
fill: #ffffff;
|
||||
}
|
||||
|
||||
.sm {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
border-radius: 22px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.md {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 36px;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.lg {
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
border-radius: 42px;
|
||||
font-size: 36px;
|
||||
}
|
||||
|
||||
.xl {
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
border-radius: 90px;
|
||||
}
|
31
src/CallTile.jsx
Normal file
31
src/CallTile.jsx
Normal file
|
@ -0,0 +1,31 @@
|
|||
import React from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { CopyButton } from "./button";
|
||||
import { Facepile } from "./Facepile";
|
||||
import { Avatar } from "./Avatar";
|
||||
import { ReactComponent as VideoIcon } from "./icons/Video.svg";
|
||||
import styles from "./CallTile.module.css";
|
||||
|
||||
export function CallTile({ name, avatarUrl, roomUrl, participants }) {
|
||||
return (
|
||||
<Link to={roomUrl} className={styles.callTile}>
|
||||
<Avatar
|
||||
size="md"
|
||||
bgKey={name}
|
||||
src={avatarUrl}
|
||||
fallback={<VideoIcon width={16} height={16} />}
|
||||
className={styles.avatar}
|
||||
/>
|
||||
<div className={styles.callInfo}>
|
||||
<h5>{name}</h5>
|
||||
<p>{roomUrl}</p>
|
||||
{participants && <Facepile participants={participants} />}
|
||||
</div>
|
||||
<CopyButton
|
||||
className={styles.copyButton}
|
||||
variant="icon"
|
||||
value={roomUrl}
|
||||
/>
|
||||
</Link>
|
||||
);
|
||||
}
|
54
src/CallTile.module.css
Normal file
54
src/CallTile.module.css
Normal file
|
@ -0,0 +1,54 @@
|
|||
.callTile {
|
||||
display: flex;
|
||||
width: 329px;
|
||||
height: 94px;
|
||||
padding: 12px;
|
||||
text-decoration: none;
|
||||
background-color: var(--bgColor2);
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.avatar,
|
||||
.copyButton {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.callInfo {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
padding: 0 16px;
|
||||
color: var(--textColor1);
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.callInfo > * {
|
||||
margin-top: 0;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.callInfo > :last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.callInfo h5 {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.callInfo p {
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.copyButton {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
|
@ -1,32 +1,32 @@
|
|||
import React from "react";
|
||||
import styles from "./Facepile.module.css";
|
||||
import ColorHash from "color-hash";
|
||||
import classNames from "classnames";
|
||||
import { Avatar } from "./Avatar";
|
||||
|
||||
const colorHash = new ColorHash({ lightness: 0.3 });
|
||||
|
||||
export function Facepile({ participants }) {
|
||||
export function Facepile({ className, participants, ...rest }) {
|
||||
return (
|
||||
<div
|
||||
className={styles.facepile}
|
||||
className={classNames(styles.facepile, className)}
|
||||
title={participants.map((member) => member.name).join(", ")}
|
||||
{...rest}
|
||||
>
|
||||
{participants.slice(0, 3).map((member) => (
|
||||
<div
|
||||
{participants.slice(0, 3).map((member, i) => (
|
||||
<Avatar
|
||||
key={member.userId}
|
||||
size="sm"
|
||||
fallback={member.name.slice(0, 1).toUpperCase()}
|
||||
className={styles.avatar}
|
||||
style={{ backgroundColor: colorHash.hex(member.name) }}
|
||||
>
|
||||
<span>{member.name.slice(0, 1).toUpperCase()}</span>
|
||||
</div>
|
||||
style={{ left: i * 22 }}
|
||||
/>
|
||||
))}
|
||||
{participants.length > 3 && (
|
||||
<div
|
||||
<Avatar
|
||||
key="additional"
|
||||
className={classNames(styles.avatar, styles.additional)}
|
||||
>
|
||||
<span>{`+${participants.length - 3}`}</span>
|
||||
</div>
|
||||
size="sm"
|
||||
fallback={`+${participants.length - 3}`}
|
||||
className={styles.avatar}
|
||||
style={{ left: 3 * 22 }}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -1,31 +1,11 @@
|
|||
.facepile {
|
||||
display: flex;
|
||||
margin: 0 16px;
|
||||
width: 100%;
|
||||
height: 24px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.facepile .avatar {
|
||||
position: relative;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 20px;
|
||||
background-color: var(--primaryColor);
|
||||
}
|
||||
|
||||
.facepile .avatar > * {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
pointer-events: none;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.facepile .avatar span {
|
||||
font-size: 14px;
|
||||
width: 20px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.facepile .avatar.additional span {
|
||||
font-size: 12px;
|
||||
top: 0;
|
||||
border: 1px solid var(--bgColor2);
|
||||
}
|
||||
|
|
251
src/Home.jsx
251
src/Home.jsx
|
@ -15,25 +15,21 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React, { useCallback, useState } from "react";
|
||||
import { useHistory, Link } from "react-router-dom";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import {
|
||||
useGroupCallRooms,
|
||||
usePublicRooms,
|
||||
} from "./ConferenceCallManagerHooks";
|
||||
import { Header, HeaderLogo, LeftNav, RightNav } from "./Header";
|
||||
import ColorHash from "color-hash";
|
||||
import styles from "./Home.module.css";
|
||||
import { FieldRow, InputField, ErrorMessage } from "./Input";
|
||||
import { Center, Content, Modal } from "./Layout";
|
||||
import {
|
||||
GroupCallIntent,
|
||||
GroupCallType,
|
||||
} 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 });
|
||||
import { CallTile } from "./CallTile";
|
||||
|
||||
function roomAliasFromRoomName(roomName) {
|
||||
return roomName
|
||||
|
@ -46,10 +42,8 @@ function roomAliasFromRoomName(roomName) {
|
|||
export function Home({ client, onLogout }) {
|
||||
const history = useHistory();
|
||||
const [roomName, setRoomName] = useState("");
|
||||
const [roomAlias, setRoomAlias] = useState("");
|
||||
const [guestAccess, setGuestAccess] = useState(false);
|
||||
const [createRoomError, setCreateRoomError] = useState();
|
||||
const [showAdvanced, setShowAdvanced] = useState();
|
||||
const rooms = useGroupCallRooms(client);
|
||||
const publicRooms = usePublicRooms(
|
||||
client,
|
||||
|
@ -110,131 +104,142 @@ export function Home({ client, onLogout }) {
|
|||
|
||||
const data = new FormData(e.target);
|
||||
const roomName = data.get("roomName");
|
||||
const roomAlias = data.get("roomAlias");
|
||||
const guestAccess = data.get("guestAccess");
|
||||
|
||||
createRoom(roomName, roomAlias, guestAccess).catch((error) => {
|
||||
setCreateRoomError(error);
|
||||
setShowAdvanced(true);
|
||||
});
|
||||
createRoom(roomName, roomAliasFromRoomName(roomName), guestAccess).catch(
|
||||
(error) => {
|
||||
setCreateRoomError(error);
|
||||
setShowAdvanced(true);
|
||||
}
|
||||
);
|
||||
},
|
||||
[client]
|
||||
);
|
||||
|
||||
const [roomId, setRoomId] = useState("");
|
||||
|
||||
const onJoinRoom = useCallback(
|
||||
(e) => {
|
||||
e.preventDefault();
|
||||
const data = new FormData(e.target);
|
||||
const roomId = data.get("roomId");
|
||||
history.push(`/room/${roomId}`);
|
||||
},
|
||||
[history]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header>
|
||||
<LeftNav>
|
||||
<HeaderLogo />
|
||||
</LeftNav>
|
||||
<RightNav>
|
||||
<UserMenu
|
||||
signedIn
|
||||
userName={client.getUserIdLocalpart()}
|
||||
onLogout={onLogout}
|
||||
/>
|
||||
</RightNav>
|
||||
</Header>
|
||||
<Content>
|
||||
<Center>
|
||||
<Modal>
|
||||
<section>
|
||||
<form onSubmit={onCreateRoom}>
|
||||
<h2>Create New Room</h2>
|
||||
<FieldRow>
|
||||
<InputField
|
||||
id="roomName"
|
||||
name="roomName"
|
||||
label="Room Name"
|
||||
type="text"
|
||||
required
|
||||
autoComplete="off"
|
||||
placeholder="Room Name"
|
||||
value={roomName}
|
||||
onChange={(e) => setRoomName(e.target.value)}
|
||||
/>
|
||||
<div class={styles.home}>
|
||||
<div className={styles.left}>
|
||||
<Header>
|
||||
<LeftNav>
|
||||
<HeaderLogo />
|
||||
</LeftNav>
|
||||
</Header>
|
||||
<div className={styles.content}>
|
||||
<div className={styles.centered}>
|
||||
<form onSubmit={onJoinRoom}>
|
||||
<h1>Join a call</h1>
|
||||
<FieldRow className={styles.fieldRow}>
|
||||
<InputField
|
||||
id="roomId"
|
||||
name="roomId"
|
||||
label="Call ID"
|
||||
type="text"
|
||||
required
|
||||
autoComplete="off"
|
||||
placeholder="Call ID"
|
||||
value={roomId}
|
||||
onChange={(e) => setRoomId(e.target.value)}
|
||||
/>
|
||||
</FieldRow>
|
||||
<FieldRow className={styles.fieldRow}>
|
||||
<Button className={styles.button} type="submit">
|
||||
Join call
|
||||
</Button>
|
||||
</FieldRow>
|
||||
</form>
|
||||
<hr />
|
||||
<form onSubmit={onCreateRoom}>
|
||||
<h1>Create a call</h1>
|
||||
<FieldRow className={styles.fieldRow}>
|
||||
<InputField
|
||||
id="roomName"
|
||||
name="roomName"
|
||||
label="Room Name"
|
||||
type="text"
|
||||
required
|
||||
autoComplete="off"
|
||||
placeholder="Room Name"
|
||||
value={roomName}
|
||||
onChange={(e) => setRoomName(e.target.value)}
|
||||
/>
|
||||
</FieldRow>
|
||||
<FieldRow>
|
||||
<InputField
|
||||
id="guestAccess"
|
||||
name="guestAccess"
|
||||
label="Allow Guest Access"
|
||||
type="checkbox"
|
||||
checked={guestAccess}
|
||||
onChange={(e) => setGuestAccess(e.target.checked)}
|
||||
/>
|
||||
</FieldRow>
|
||||
{createRoomError && (
|
||||
<FieldRow className={styles.fieldRow}>
|
||||
<ErrorMessage>{createRoomError.message}</ErrorMessage>
|
||||
</FieldRow>
|
||||
<details open={showAdvanced}>
|
||||
<summary>Advanced</summary>
|
||||
<FieldRow>
|
||||
<InputField
|
||||
id="roomAlias"
|
||||
name="roomAlias"
|
||||
label="Room Alias"
|
||||
type="text"
|
||||
autoComplete="off"
|
||||
placeholder="Room Alias"
|
||||
value={roomAlias || roomAliasFromRoomName(roomName)}
|
||||
onChange={(e) => setRoomAlias(e.target.value)}
|
||||
/>
|
||||
</FieldRow>
|
||||
<FieldRow>
|
||||
<InputField
|
||||
id="guestAccess"
|
||||
name="guestAccess"
|
||||
label="Allow Guest Access"
|
||||
type="checkbox"
|
||||
checked={guestAccess}
|
||||
onChange={(e) => setGuestAccess(e.target.checked)}
|
||||
/>
|
||||
</FieldRow>
|
||||
</details>
|
||||
{createRoomError && (
|
||||
<FieldRow>
|
||||
<ErrorMessage>{createRoomError.message}</ErrorMessage>
|
||||
</FieldRow>
|
||||
)}
|
||||
<FieldRow rightAlign>
|
||||
<Button type="submit">Create Room</Button>
|
||||
</FieldRow>
|
||||
</form>
|
||||
</section>
|
||||
{publicRooms.length > 0 && (
|
||||
<section>
|
||||
<h3>Public Rooms</h3>
|
||||
<div className={styles.roomList}>
|
||||
{publicRooms.map((room) => (
|
||||
<Link
|
||||
className={styles.roomListItem}
|
||||
key={room.room_id}
|
||||
to={`/room/${room.room_id}`}
|
||||
>
|
||||
<div
|
||||
className={styles.roomAvatar}
|
||||
style={{ backgroundColor: colorHash.hex(room.name) }}
|
||||
>
|
||||
<span>{room.name.slice(0, 1)}</span>
|
||||
</div>
|
||||
<div className={styles.roomName}>{room.name}</div>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
<section>
|
||||
<h3>Recent Rooms</h3>
|
||||
)}
|
||||
<FieldRow className={styles.fieldRow}>
|
||||
<Button className={styles.button} type="submit">
|
||||
Create call
|
||||
</Button>
|
||||
</FieldRow>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.right}>
|
||||
<Header>
|
||||
<LeftNav />
|
||||
<RightNav>
|
||||
<UserMenu
|
||||
signedIn
|
||||
userName={client.getUserIdLocalpart()}
|
||||
onLogout={onLogout}
|
||||
/>
|
||||
</RightNav>
|
||||
</Header>
|
||||
<div className={styles.content}>
|
||||
{publicRooms.length > 0 && (
|
||||
<>
|
||||
<h3>Public Calls</h3>
|
||||
<div className={styles.roomList}>
|
||||
{rooms.map(({ room, participants }) => (
|
||||
<Link
|
||||
className={styles.roomListItem}
|
||||
key={room.roomId}
|
||||
to={`/room/${room.getCanonicalAlias() || room.roomId}`}
|
||||
>
|
||||
<div
|
||||
className={styles.roomAvatar}
|
||||
style={{ backgroundColor: colorHash.hex(room.name) }}
|
||||
>
|
||||
<span>{room.name.slice(0, 1)}</span>
|
||||
</div>
|
||||
<div className={styles.roomName}>{room.name}</div>
|
||||
<Facepile participants={participants} />
|
||||
</Link>
|
||||
{publicRooms.map((room) => (
|
||||
<CallTile
|
||||
key={room.room_id}
|
||||
name={room.name}
|
||||
avatarUrl={null}
|
||||
roomUrl={`/room/${room.room_id}`}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
</Modal>
|
||||
</Center>
|
||||
</Content>
|
||||
</>
|
||||
</>
|
||||
)}
|
||||
<h3>Recent Calls</h3>
|
||||
<div className={styles.roomList}>
|
||||
{rooms.map(({ room, participants }) => (
|
||||
<CallTile
|
||||
key={room.roomId}
|
||||
name={room.name}
|
||||
avatarUrl={null}
|
||||
roomUrl={`/room/${room.getCanonicalAlias() || room.roomId}`}
|
||||
participants={participants}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,47 +1,109 @@
|
|||
.roomList {
|
||||
.home {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.roomListItem {
|
||||
margin-bottom: 4px;
|
||||
padding: 4px;
|
||||
.left,
|
||||
.right {
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
color: var(--textColor1);
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.left {
|
||||
background-color: var(--bgColor2);
|
||||
}
|
||||
|
||||
.centered {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
max-width: 512px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.left .content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-top: 113px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.roomListItem:hover {
|
||||
background-color: rgba(141, 151, 165, 0.2);
|
||||
border-radius: 8px;
|
||||
color: var(--textColor1);
|
||||
.left .content form > * {
|
||||
margin-top: 0;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.roomAvatar {
|
||||
position: relative;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 32px;
|
||||
flex-shrink: 0;
|
||||
.left .content form > :last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.roomAvatar > * {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
color: #fff;
|
||||
.left .content hr {
|
||||
width: 100%;
|
||||
border: none;
|
||||
border-top: 1px solid var(--bgColor4);
|
||||
color: var(--textColor2);
|
||||
overflow: visible;
|
||||
text-align: center;
|
||||
pointer-events: none;
|
||||
font-weight: 400;
|
||||
height: 5px;
|
||||
font-weight: 600;
|
||||
font-size: 15px;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.roomAvatar span {
|
||||
font-size: 20.8px;
|
||||
width: 32px;
|
||||
line-height: 32px;
|
||||
.left .content hr:after {
|
||||
background-color: var(--bgColor2);
|
||||
content: "OR";
|
||||
padding: 0 12px;
|
||||
position: relative;
|
||||
top: -12px;
|
||||
}
|
||||
|
||||
.roomName {
|
||||
margin-left: 8px;
|
||||
font-size: 14px;
|
||||
line-height: 18px;
|
||||
.left .content form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 92px;
|
||||
}
|
||||
|
||||
.fieldRow {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.button {
|
||||
height: 40px;
|
||||
width: 100%;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.left .content form:first-child {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.left .content form:last-child {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.right .content {
|
||||
padding: 113px 40px 40px 40px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.right .content h3:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.roomList {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-start;
|
||||
gap: 24px;
|
||||
flex: 1;
|
||||
}
|
||||
|
|
|
@ -35,12 +35,12 @@
|
|||
|
||||
.inputField input {
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
font-size: 15px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
padding: 8px 9px;
|
||||
padding: 11px 9px;
|
||||
color: var(--textColor1);
|
||||
background-color: var(--bgColor2);
|
||||
background-color: var(--bgColor1);
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
@ -60,11 +60,11 @@
|
|||
top 0.25s ease-out 0.1s, background-color 0.25s ease-out 0.1s;
|
||||
color: var(--textColor1);
|
||||
background-color: transparent;
|
||||
font-size: 14px;
|
||||
font-size: 15px;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
margin: 7px 8px;
|
||||
margin: 9px 8px;
|
||||
padding: 2px;
|
||||
pointer-events: none;
|
||||
overflow: hidden;
|
||||
|
|
|
@ -31,6 +31,7 @@ import {
|
|||
RightNav,
|
||||
RoomHeaderInfo,
|
||||
RoomSetupHeaderInfo,
|
||||
HeaderLogo,
|
||||
} from "./Header";
|
||||
import { GroupCallState } from "matrix-js-sdk/src/webrtc/groupCall";
|
||||
import VideoGrid, {
|
||||
|
|
|
@ -18,7 +18,10 @@ const variantToClassName = {
|
|||
};
|
||||
|
||||
export const Button = forwardRef(
|
||||
({ variant = "default", on, off, className, children, ...rest }, ref) => {
|
||||
(
|
||||
{ variant = "default", on, off, iconStyle, className, children, ...rest },
|
||||
ref
|
||||
) => {
|
||||
const buttonRef = useObjectRef(ref);
|
||||
const { buttonProps } = useButton(rest, buttonRef);
|
||||
|
||||
|
@ -33,10 +36,15 @@ export const Button = forwardRef(
|
|||
|
||||
return (
|
||||
<button
|
||||
className={classNames(variantToClassName[variant], className, {
|
||||
[styles.on]: on,
|
||||
[styles.off]: off,
|
||||
})}
|
||||
className={classNames(
|
||||
variantToClassName[variant],
|
||||
styles[iconStyle],
|
||||
className,
|
||||
{
|
||||
[styles.on]: on,
|
||||
[styles.off]: off,
|
||||
}
|
||||
)}
|
||||
{...filteredButtonProps}
|
||||
ref={buttonRef}
|
||||
>
|
||||
|
|
|
@ -52,22 +52,22 @@ limitations under the License.
|
|||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.iconButton svg * {
|
||||
.iconButton:not(.stroke) svg * {
|
||||
fill: #8e99a4;
|
||||
}
|
||||
|
||||
.iconButton:hover svg * {
|
||||
.iconButton:not(.stroke):hover svg * {
|
||||
fill: #8d97a5;
|
||||
}
|
||||
|
||||
.iconButton:hover svg * {
|
||||
fill: #8d97a5;
|
||||
}
|
||||
|
||||
.iconButton.on svg * {
|
||||
.iconButton.on:not(.stroke) svg * {
|
||||
fill: #0dbd8b;
|
||||
}
|
||||
|
||||
.iconButton.on.stroke svg * {
|
||||
stroke: #0dbd8b;
|
||||
}
|
||||
|
||||
.hangupButton,
|
||||
.hangupButton:hover {
|
||||
background-color: #ff5b55;
|
||||
|
|
|
@ -4,19 +4,25 @@ 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 }) {
|
||||
export function CopyButton({ value, children, variant, ...rest }) {
|
||||
const [isCopied, setCopied] = useClipboard(value, { successDuration: 3000 });
|
||||
|
||||
return (
|
||||
<Button {...rest} variant="copy" on={isCopied} onPress={setCopied}>
|
||||
<Button
|
||||
{...rest}
|
||||
variant={variant || "copy"}
|
||||
on={isCopied}
|
||||
onPress={setCopied}
|
||||
iconStyle={isCopied ? "stroke" : "fill"}
|
||||
>
|
||||
{isCopied ? (
|
||||
<>
|
||||
<span>Copied!</span>
|
||||
{variant !== "icon" && <span>Copied!</span>}
|
||||
<CheckIcon />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<span>{children || value}</span>
|
||||
{variant !== "icon" && <span>{children || value}</span>}
|
||||
<CopyIcon />
|
||||
</>
|
||||
)}
|
||||
|
|
Loading…
Reference in a new issue