From 20350e66a2fb043364121284d47c2637d5661ce0 Mon Sep 17 00:00:00 2001 From: Robert Long Date: Tue, 7 Dec 2021 17:59:55 -0800 Subject: [PATCH] Home page styling --- src/Avatar.jsx | 58 ++++++++ src/Avatar.module.css | 46 +++++++ src/CallTile.jsx | 31 +++++ src/CallTile.module.css | 54 ++++++++ src/Facepile.jsx | 32 ++--- src/Facepile.module.css | 30 +---- src/Home.jsx | 251 ++++++++++++++++++----------------- src/Home.module.css | 124 ++++++++++++----- src/Input.module.css | 10 +- src/Room.jsx | 1 + src/button/Button.jsx | 18 ++- src/button/Button.module.css | 14 +- src/button/CopyButton.jsx | 14 +- 13 files changed, 467 insertions(+), 216 deletions(-) create mode 100644 src/Avatar.jsx create mode 100644 src/Avatar.module.css create mode 100644 src/CallTile.jsx create mode 100644 src/CallTile.module.css diff --git a/src/Avatar.jsx b/src/Avatar.jsx new file mode 100644 index 0000000..85b6255 --- /dev/null +++ b/src/Avatar.jsx @@ -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 ( +
+ {src ? ( + + ) : typeof fallback === "string" ? ( + {fallback} + ) : ( + fallback + )} +
+ ); +} diff --git a/src/Avatar.module.css b/src/Avatar.module.css new file mode 100644 index 0000000..0fe6ff5 --- /dev/null +++ b/src/Avatar.module.css @@ -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; +} diff --git a/src/CallTile.jsx b/src/CallTile.jsx new file mode 100644 index 0000000..94b7e42 --- /dev/null +++ b/src/CallTile.jsx @@ -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 ( + + } + className={styles.avatar} + /> +
+
{name}
+

{roomUrl}

+ {participants && } +
+ + + ); +} diff --git a/src/CallTile.module.css b/src/CallTile.module.css new file mode 100644 index 0000000..6902977 --- /dev/null +++ b/src/CallTile.module.css @@ -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; +} diff --git a/src/Facepile.jsx b/src/Facepile.jsx index ae97161..3071d8d 100644 --- a/src/Facepile.jsx +++ b/src/Facepile.jsx @@ -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 (
member.name).join(", ")} + {...rest} > - {participants.slice(0, 3).map((member) => ( -
( + - {member.name.slice(0, 1).toUpperCase()} -
+ style={{ left: i * 22 }} + /> ))} {participants.length > 3 && ( -
- {`+${participants.length - 3}`} -
+ size="sm" + fallback={`+${participants.length - 3}`} + className={styles.avatar} + style={{ left: 3 * 22 }} + /> )}
); diff --git a/src/Facepile.module.css b/src/Facepile.module.css index 9654149..4add90e 100644 --- a/src/Facepile.module.css +++ b/src/Facepile.module.css @@ -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); } diff --git a/src/Home.jsx b/src/Home.jsx index 91439cf..282d634 100644 --- a/src/Home.jsx +++ b/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 ( - <> -
- - - - - - -
- -
- -
-
-

Create New Room

- - setRoomName(e.target.value)} - /> +
+
+
+ + + +
+
+
+ +

Join a call

+ + setRoomId(e.target.value)} + /> + + + + + +
+
+

Create a call

+ + setRoomName(e.target.value)} + /> + + + setGuestAccess(e.target.checked)} + /> + + {createRoomError && ( + + {createRoomError.message} -
- Advanced - - setRoomAlias(e.target.value)} - /> - - - setGuestAccess(e.target.checked)} - /> - -
- {createRoomError && ( - - {createRoomError.message} - - )} - - - -
-
- {publicRooms.length > 0 && ( -
-

Public Rooms

-
- {publicRooms.map((room) => ( - -
- {room.name.slice(0, 1)} -
-
{room.name}
- - ))} -
-
- )} -
-

Recent Rooms

+ )} + + + + + + + +
+
+ + + + +
+
+ {publicRooms.length > 0 && ( + <> +

Public Calls

- {rooms.map(({ room, participants }) => ( - -
- {room.name.slice(0, 1)} -
-
{room.name}
- - + {publicRooms.map((room) => ( + ))}
-
-
-
-
- + + )} +

Recent Calls

+
+ {rooms.map(({ room, participants }) => ( + + ))} +
+ + + ); } diff --git a/src/Home.module.css b/src/Home.module.css index bfb82dc..182cf73 100644 --- a/src/Home.module.css +++ b/src/Home.module.css @@ -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; } diff --git a/src/Input.module.css b/src/Input.module.css index 8be140e..08ac6a3 100644 --- a/src/Input.module.css +++ b/src/Input.module.css @@ -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; diff --git a/src/Room.jsx b/src/Room.jsx index e728a72..ea89941 100644 --- a/src/Room.jsx +++ b/src/Room.jsx @@ -31,6 +31,7 @@ import { RightNav, RoomHeaderInfo, RoomSetupHeaderInfo, + HeaderLogo, } from "./Header"; import { GroupCallState } from "matrix-js-sdk/src/webrtc/groupCall"; import VideoGrid, { diff --git a/src/button/Button.jsx b/src/button/Button.jsx index d7270f2..664f49d 100644 --- a/src/button/Button.jsx +++ b/src/button/Button.jsx @@ -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 (