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 React from "react";
|
||||||
import styles from "./Facepile.module.css";
|
import styles from "./Facepile.module.css";
|
||||||
import ColorHash from "color-hash";
|
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
|
import { Avatar } from "./Avatar";
|
||||||
|
|
||||||
const colorHash = new ColorHash({ lightness: 0.3 });
|
export function Facepile({ className, participants, ...rest }) {
|
||||||
|
|
||||||
export function Facepile({ participants }) {
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={styles.facepile}
|
className={classNames(styles.facepile, className)}
|
||||||
title={participants.map((member) => member.name).join(", ")}
|
title={participants.map((member) => member.name).join(", ")}
|
||||||
|
{...rest}
|
||||||
>
|
>
|
||||||
{participants.slice(0, 3).map((member) => (
|
{participants.slice(0, 3).map((member, i) => (
|
||||||
<div
|
<Avatar
|
||||||
key={member.userId}
|
key={member.userId}
|
||||||
|
size="sm"
|
||||||
|
fallback={member.name.slice(0, 1).toUpperCase()}
|
||||||
className={styles.avatar}
|
className={styles.avatar}
|
||||||
style={{ backgroundColor: colorHash.hex(member.name) }}
|
style={{ left: i * 22 }}
|
||||||
>
|
/>
|
||||||
<span>{member.name.slice(0, 1).toUpperCase()}</span>
|
|
||||||
</div>
|
|
||||||
))}
|
))}
|
||||||
{participants.length > 3 && (
|
{participants.length > 3 && (
|
||||||
<div
|
<Avatar
|
||||||
key="additional"
|
key="additional"
|
||||||
className={classNames(styles.avatar, styles.additional)}
|
size="sm"
|
||||||
>
|
fallback={`+${participants.length - 3}`}
|
||||||
<span>{`+${participants.length - 3}`}</span>
|
className={styles.avatar}
|
||||||
</div>
|
style={{ left: 3 * 22 }}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,31 +1,11 @@
|
||||||
.facepile {
|
.facepile {
|
||||||
display: flex;
|
width: 100%;
|
||||||
margin: 0 16px;
|
height: 24px;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.facepile .avatar {
|
.facepile .avatar {
|
||||||
position: relative;
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
border-radius: 20px;
|
|
||||||
background-color: var(--primaryColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
.facepile .avatar > * {
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 0;
|
top: 0;
|
||||||
color: #fff;
|
border: 1px solid var(--bgColor2);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
251
src/Home.jsx
251
src/Home.jsx
|
@ -15,25 +15,21 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useCallback, useState } from "react";
|
import React, { useCallback, useState } from "react";
|
||||||
import { useHistory, Link } from "react-router-dom";
|
import { useHistory } from "react-router-dom";
|
||||||
import {
|
import {
|
||||||
useGroupCallRooms,
|
useGroupCallRooms,
|
||||||
usePublicRooms,
|
usePublicRooms,
|
||||||
} from "./ConferenceCallManagerHooks";
|
} from "./ConferenceCallManagerHooks";
|
||||||
import { Header, HeaderLogo, LeftNav, RightNav } from "./Header";
|
import { Header, HeaderLogo, LeftNav, RightNav } from "./Header";
|
||||||
import ColorHash from "color-hash";
|
|
||||||
import styles from "./Home.module.css";
|
import styles from "./Home.module.css";
|
||||||
import { FieldRow, InputField, ErrorMessage } from "./Input";
|
import { FieldRow, InputField, ErrorMessage } from "./Input";
|
||||||
import { Center, Content, Modal } from "./Layout";
|
|
||||||
import {
|
import {
|
||||||
GroupCallIntent,
|
GroupCallIntent,
|
||||||
GroupCallType,
|
GroupCallType,
|
||||||
} from "matrix-js-sdk/src/browser-index";
|
} from "matrix-js-sdk/src/browser-index";
|
||||||
import { Facepile } from "./Facepile";
|
|
||||||
import { UserMenu } from "./UserMenu";
|
import { UserMenu } from "./UserMenu";
|
||||||
import { Button } from "./button";
|
import { Button } from "./button";
|
||||||
|
import { CallTile } from "./CallTile";
|
||||||
const colorHash = new ColorHash({ lightness: 0.3 });
|
|
||||||
|
|
||||||
function roomAliasFromRoomName(roomName) {
|
function roomAliasFromRoomName(roomName) {
|
||||||
return roomName
|
return roomName
|
||||||
|
@ -46,10 +42,8 @@ function roomAliasFromRoomName(roomName) {
|
||||||
export function Home({ client, onLogout }) {
|
export function Home({ client, onLogout }) {
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const [roomName, setRoomName] = useState("");
|
const [roomName, setRoomName] = useState("");
|
||||||
const [roomAlias, setRoomAlias] = useState("");
|
|
||||||
const [guestAccess, setGuestAccess] = useState(false);
|
const [guestAccess, setGuestAccess] = useState(false);
|
||||||
const [createRoomError, setCreateRoomError] = useState();
|
const [createRoomError, setCreateRoomError] = useState();
|
||||||
const [showAdvanced, setShowAdvanced] = useState();
|
|
||||||
const rooms = useGroupCallRooms(client);
|
const rooms = useGroupCallRooms(client);
|
||||||
const publicRooms = usePublicRooms(
|
const publicRooms = usePublicRooms(
|
||||||
client,
|
client,
|
||||||
|
@ -110,131 +104,142 @@ export function Home({ client, onLogout }) {
|
||||||
|
|
||||||
const data = new FormData(e.target);
|
const data = new FormData(e.target);
|
||||||
const roomName = data.get("roomName");
|
const roomName = data.get("roomName");
|
||||||
const roomAlias = data.get("roomAlias");
|
|
||||||
const guestAccess = data.get("guestAccess");
|
const guestAccess = data.get("guestAccess");
|
||||||
|
|
||||||
createRoom(roomName, roomAlias, guestAccess).catch((error) => {
|
createRoom(roomName, roomAliasFromRoomName(roomName), guestAccess).catch(
|
||||||
setCreateRoomError(error);
|
(error) => {
|
||||||
setShowAdvanced(true);
|
setCreateRoomError(error);
|
||||||
});
|
setShowAdvanced(true);
|
||||||
|
}
|
||||||
|
);
|
||||||
},
|
},
|
||||||
[client]
|
[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 (
|
return (
|
||||||
<>
|
<div class={styles.home}>
|
||||||
<Header>
|
<div className={styles.left}>
|
||||||
<LeftNav>
|
<Header>
|
||||||
<HeaderLogo />
|
<LeftNav>
|
||||||
</LeftNav>
|
<HeaderLogo />
|
||||||
<RightNav>
|
</LeftNav>
|
||||||
<UserMenu
|
</Header>
|
||||||
signedIn
|
<div className={styles.content}>
|
||||||
userName={client.getUserIdLocalpart()}
|
<div className={styles.centered}>
|
||||||
onLogout={onLogout}
|
<form onSubmit={onJoinRoom}>
|
||||||
/>
|
<h1>Join a call</h1>
|
||||||
</RightNav>
|
<FieldRow className={styles.fieldRow}>
|
||||||
</Header>
|
<InputField
|
||||||
<Content>
|
id="roomId"
|
||||||
<Center>
|
name="roomId"
|
||||||
<Modal>
|
label="Call ID"
|
||||||
<section>
|
type="text"
|
||||||
<form onSubmit={onCreateRoom}>
|
required
|
||||||
<h2>Create New Room</h2>
|
autoComplete="off"
|
||||||
<FieldRow>
|
placeholder="Call ID"
|
||||||
<InputField
|
value={roomId}
|
||||||
id="roomName"
|
onChange={(e) => setRoomId(e.target.value)}
|
||||||
name="roomName"
|
/>
|
||||||
label="Room Name"
|
</FieldRow>
|
||||||
type="text"
|
<FieldRow className={styles.fieldRow}>
|
||||||
required
|
<Button className={styles.button} type="submit">
|
||||||
autoComplete="off"
|
Join call
|
||||||
placeholder="Room Name"
|
</Button>
|
||||||
value={roomName}
|
</FieldRow>
|
||||||
onChange={(e) => setRoomName(e.target.value)}
|
</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>
|
</FieldRow>
|
||||||
<details open={showAdvanced}>
|
)}
|
||||||
<summary>Advanced</summary>
|
<FieldRow className={styles.fieldRow}>
|
||||||
<FieldRow>
|
<Button className={styles.button} type="submit">
|
||||||
<InputField
|
Create call
|
||||||
id="roomAlias"
|
</Button>
|
||||||
name="roomAlias"
|
</FieldRow>
|
||||||
label="Room Alias"
|
</form>
|
||||||
type="text"
|
</div>
|
||||||
autoComplete="off"
|
</div>
|
||||||
placeholder="Room Alias"
|
</div>
|
||||||
value={roomAlias || roomAliasFromRoomName(roomName)}
|
<div className={styles.right}>
|
||||||
onChange={(e) => setRoomAlias(e.target.value)}
|
<Header>
|
||||||
/>
|
<LeftNav />
|
||||||
</FieldRow>
|
<RightNav>
|
||||||
<FieldRow>
|
<UserMenu
|
||||||
<InputField
|
signedIn
|
||||||
id="guestAccess"
|
userName={client.getUserIdLocalpart()}
|
||||||
name="guestAccess"
|
onLogout={onLogout}
|
||||||
label="Allow Guest Access"
|
/>
|
||||||
type="checkbox"
|
</RightNav>
|
||||||
checked={guestAccess}
|
</Header>
|
||||||
onChange={(e) => setGuestAccess(e.target.checked)}
|
<div className={styles.content}>
|
||||||
/>
|
{publicRooms.length > 0 && (
|
||||||
</FieldRow>
|
<>
|
||||||
</details>
|
<h3>Public Calls</h3>
|
||||||
{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>
|
|
||||||
<div className={styles.roomList}>
|
<div className={styles.roomList}>
|
||||||
{rooms.map(({ room, participants }) => (
|
{publicRooms.map((room) => (
|
||||||
<Link
|
<CallTile
|
||||||
className={styles.roomListItem}
|
key={room.room_id}
|
||||||
key={room.roomId}
|
name={room.name}
|
||||||
to={`/room/${room.getCanonicalAlias() || room.roomId}`}
|
avatarUrl={null}
|
||||||
>
|
roomUrl={`/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>
|
|
||||||
<Facepile participants={participants} />
|
|
||||||
</Link>
|
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</>
|
||||||
</Modal>
|
)}
|
||||||
</Center>
|
<h3>Recent Calls</h3>
|
||||||
</Content>
|
<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 {
|
.left,
|
||||||
margin-bottom: 4px;
|
.right {
|
||||||
padding: 4px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
cursor: pointer;
|
flex-direction: column;
|
||||||
text-decoration: none;
|
flex: 1;
|
||||||
color: var(--textColor1);
|
}
|
||||||
|
|
||||||
|
.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;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.roomListItem:hover {
|
.left .content form > * {
|
||||||
background-color: rgba(141, 151, 165, 0.2);
|
margin-top: 0;
|
||||||
border-radius: 8px;
|
margin-bottom: 24px;
|
||||||
color: var(--textColor1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.roomAvatar {
|
.left .content form > :last-child {
|
||||||
position: relative;
|
margin-bottom: 0;
|
||||||
width: 32px;
|
|
||||||
height: 32px;
|
|
||||||
border-radius: 32px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.roomAvatar > * {
|
.left .content hr {
|
||||||
position: absolute;
|
width: 100%;
|
||||||
left: 0;
|
border: none;
|
||||||
color: #fff;
|
border-top: 1px solid var(--bgColor4);
|
||||||
|
color: var(--textColor2);
|
||||||
|
overflow: visible;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
pointer-events: none;
|
height: 5px;
|
||||||
font-weight: 400;
|
font-weight: 600;
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.roomAvatar span {
|
.left .content hr:after {
|
||||||
font-size: 20.8px;
|
background-color: var(--bgColor2);
|
||||||
width: 32px;
|
content: "OR";
|
||||||
line-height: 32px;
|
padding: 0 12px;
|
||||||
|
position: relative;
|
||||||
|
top: -12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.roomName {
|
.left .content form {
|
||||||
margin-left: 8px;
|
display: flex;
|
||||||
font-size: 14px;
|
flex-direction: column;
|
||||||
line-height: 18px;
|
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 {
|
.inputField input {
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-size: 14px;
|
font-size: 15px;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
padding: 8px 9px;
|
padding: 11px 9px;
|
||||||
color: var(--textColor1);
|
color: var(--textColor1);
|
||||||
background-color: var(--bgColor2);
|
background-color: var(--bgColor1);
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
@ -60,11 +60,11 @@
|
||||||
top 0.25s ease-out 0.1s, background-color 0.25s ease-out 0.1s;
|
top 0.25s ease-out 0.1s, background-color 0.25s ease-out 0.1s;
|
||||||
color: var(--textColor1);
|
color: var(--textColor1);
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
font-size: 14px;
|
font-size: 15px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 0;
|
left: 0;
|
||||||
top: 0;
|
top: 0;
|
||||||
margin: 7px 8px;
|
margin: 9px 8px;
|
||||||
padding: 2px;
|
padding: 2px;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
|
@ -31,6 +31,7 @@ import {
|
||||||
RightNav,
|
RightNav,
|
||||||
RoomHeaderInfo,
|
RoomHeaderInfo,
|
||||||
RoomSetupHeaderInfo,
|
RoomSetupHeaderInfo,
|
||||||
|
HeaderLogo,
|
||||||
} from "./Header";
|
} from "./Header";
|
||||||
import { GroupCallState } from "matrix-js-sdk/src/webrtc/groupCall";
|
import { GroupCallState } from "matrix-js-sdk/src/webrtc/groupCall";
|
||||||
import VideoGrid, {
|
import VideoGrid, {
|
||||||
|
|
|
@ -18,7 +18,10 @@ const variantToClassName = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Button = forwardRef(
|
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 buttonRef = useObjectRef(ref);
|
||||||
const { buttonProps } = useButton(rest, buttonRef);
|
const { buttonProps } = useButton(rest, buttonRef);
|
||||||
|
|
||||||
|
@ -33,10 +36,15 @@ export const Button = forwardRef(
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
className={classNames(variantToClassName[variant], className, {
|
className={classNames(
|
||||||
[styles.on]: on,
|
variantToClassName[variant],
|
||||||
[styles.off]: off,
|
styles[iconStyle],
|
||||||
})}
|
className,
|
||||||
|
{
|
||||||
|
[styles.on]: on,
|
||||||
|
[styles.off]: off,
|
||||||
|
}
|
||||||
|
)}
|
||||||
{...filteredButtonProps}
|
{...filteredButtonProps}
|
||||||
ref={buttonRef}
|
ref={buttonRef}
|
||||||
>
|
>
|
||||||
|
|
|
@ -52,22 +52,22 @@ limitations under the License.
|
||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.iconButton svg * {
|
.iconButton:not(.stroke) svg * {
|
||||||
fill: #8e99a4;
|
fill: #8e99a4;
|
||||||
}
|
}
|
||||||
|
|
||||||
.iconButton:hover svg * {
|
.iconButton:not(.stroke):hover svg * {
|
||||||
fill: #8d97a5;
|
fill: #8d97a5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.iconButton:hover svg * {
|
.iconButton.on:not(.stroke) svg * {
|
||||||
fill: #8d97a5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.iconButton.on svg * {
|
|
||||||
fill: #0dbd8b;
|
fill: #0dbd8b;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.iconButton.on.stroke svg * {
|
||||||
|
stroke: #0dbd8b;
|
||||||
|
}
|
||||||
|
|
||||||
.hangupButton,
|
.hangupButton,
|
||||||
.hangupButton:hover {
|
.hangupButton:hover {
|
||||||
background-color: #ff5b55;
|
background-color: #ff5b55;
|
||||||
|
|
|
@ -4,19 +4,25 @@ import { ReactComponent as CheckIcon } from "../icons/Check.svg";
|
||||||
import { ReactComponent as CopyIcon } from "../icons/Copy.svg";
|
import { ReactComponent as CopyIcon } from "../icons/Copy.svg";
|
||||||
import { Button } from "./Button";
|
import { Button } from "./Button";
|
||||||
|
|
||||||
export function CopyButton({ value, children, ...rest }) {
|
export function CopyButton({ value, children, variant, ...rest }) {
|
||||||
const [isCopied, setCopied] = useClipboard(value, { successDuration: 3000 });
|
const [isCopied, setCopied] = useClipboard(value, { successDuration: 3000 });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button {...rest} variant="copy" on={isCopied} onPress={setCopied}>
|
<Button
|
||||||
|
{...rest}
|
||||||
|
variant={variant || "copy"}
|
||||||
|
on={isCopied}
|
||||||
|
onPress={setCopied}
|
||||||
|
iconStyle={isCopied ? "stroke" : "fill"}
|
||||||
|
>
|
||||||
{isCopied ? (
|
{isCopied ? (
|
||||||
<>
|
<>
|
||||||
<span>Copied!</span>
|
{variant !== "icon" && <span>Copied!</span>}
|
||||||
<CheckIcon />
|
<CheckIcon />
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<span>{children || value}</span>
|
{variant !== "icon" && <span>{children || value}</span>}
|
||||||
<CopyIcon />
|
<CopyIcon />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
Loading…
Add table
Reference in a new issue