First pass at the new video tile designs

Here, I've begun updating the styles of video tiles to match the new designs. Not yet updated: the local volume option is supposed to go inside an overflow menu now, but I haven't gotten to that yet.

To make the outlines on hovered / speaking tiles show up properly, I have to remove the usePageFocusStyle hack, which was preventing CSS outlines from being used for anything other than focus rings. I honestly can't tell what problem it was solving in the first place: focus rings still appear to behave as expected throughout the application.
This commit is contained in:
Robin
2023-09-13 16:19:29 -04:00
parent 1f95ec7a0c
commit 915fb63356
13 changed files with 84 additions and 149 deletions

View File

@@ -16,14 +16,13 @@ limitations under the License.
.bigGrid {
display: grid;
grid-auto-rows: 163px;
gap: 8px;
grid-auto-rows: 130px;
gap: var(--cpd-space-2x);
}
@media (min-width: 800px) {
.bigGrid {
grid-auto-rows: 183px;
column-gap: 18px;
row-gap: 21px;
grid-auto-rows: 135px;
gap: var(--cpd-space-5x);
}
}

View File

@@ -957,7 +957,7 @@ function updateTiles(g: Grid, tiles: TileDescriptor<unknown>[]): Grid {
}
function updateBounds(g: Grid, bounds: RectReadOnly): Grid {
const columns = Math.max(2, Math.floor(bounds.width * 0.0045));
const columns = Math.max(2, Math.floor(bounds.width * 0.0055));
return columns === g.columns ? g : resize(g, columns);
}

View File

@@ -178,7 +178,7 @@ export function NewVideoGrid<T>({
from: ({ x, y, width, height }: Tile<T>) => ({
opacity: 0,
scale: 0,
shadow: 1,
shadow: 0,
shadowSpread: 0,
zIndex: 1,
x,
@@ -221,7 +221,7 @@ export function NewVideoGrid<T>({
? {
scale: 1,
zIndex: 1,
shadow: 1,
shadow: 0,
x: tile.x,
y: tile.y,
width: tile.width,

View File

@@ -1082,7 +1082,7 @@ export function VideoGrid<T>({
y?: number;
width?: number;
height?: number;
} = { shadow: 1, scale: 0, opacity: 0 };
} = { shadow: 0, scale: 0, opacity: 0 };
let reset = false;
if (!tilePositionsWereValid) {
@@ -1105,7 +1105,7 @@ export function VideoGrid<T>({
scale: remove ? 0 : 1,
opacity: remove ? 0 : 1,
zIndex: tilePosition.zIndex,
shadow: 1,
shadow: oneOnOneLayout && tile.item.local ? 1 : 0,
shadowSpread: oneOnOneLayout && tile.item.local ? 1 : 0,
from,
reset,

View File

@@ -20,14 +20,11 @@ limitations under the License.
top: 0;
container-name: videoTile;
container-type: size;
--tileRadius: 8px;
border-radius: var(--tileRadius);
border-radius: var(--cpd-space-4x);
overflow: hidden;
cursor: pointer;
/* HACK: This has no visual effect due to the short duration, but allows the
JS to detect movement via the transform property for audio spatialization */
transition: transform 0.000000001s;
outline: 2px solid rgba(0, 0, 0, 0);
transition: outline-radius ease 0.15s, outline-color ease 0.15s;
}
.videoTile * {
@@ -45,21 +42,14 @@ limitations under the License.
transform: scaleX(-1);
}
.videoTile::after {
position: absolute;
top: -1px;
left: -1px;
right: -1px;
bottom: -1px;
content: "";
border-radius: var(--tileRadius);
box-shadow: inset 0 0 0 4px var(--cpd-color-border-accent) !important;
opacity: 0;
transition: opacity ease 0.15s;
.videoTile.speaking {
outline: 4px solid var(--cpd-color-border-accent);
}
.videoTile.speaking::after {
opacity: 1;
@media (hover: hover) {
.videoTile:hover {
outline: 2px solid var(--cpd-color-gray-1400);
}
}
.videoTile.maximised {
@@ -73,30 +63,47 @@ limitations under the License.
object-fit: contain;
}
.infoBubble {
.nameTag {
position: absolute;
height: 24px;
padding: 0 8px;
inset-inline-start: var(--cpd-space-1x);
inset-block-end: var(--cpd-space-1x);
padding: var(--cpd-space-1x);
padding-block: var(--cpd-space-1x);
color: var(--cpd-color-text-primary);
background-color: var(--stopgap-background-85);
/* TODO: un-hardcode this color. It comes from the dark theme. */
background-color: rgba(237, 244, 252, 0.79);
display: flex;
align-items: center;
justify-content: center;
border-radius: 4px;
border-radius: var(--cpd-radius-pill-effect);
user-select: none;
max-width: calc(100% - 48px);
overflow: hidden;
z-index: 1;
box-shadow: var(--small-drop-shadow);
}
.infoBubble > svg {
height: 16px;
width: 16px;
margin-right: 4px;
:global(.cpd-theme-dark) .nameTag {
/* TODO: un-hardcode this color. It comes from the light theme. */
background-color: rgba(2, 7, 13, 0.77);
}
.infoBubble > svg * {
fill: var(--cpd-color-icon-primary);
.nameTag > svg {
flex-shrink: 0;
}
.nameTag > svg[data-muted="true"] {
color: var(--cpd-color-icon-secondary);
}
.nameTag > svg[data-muted="false"] {
color: var(--cpd-color-icon-primary);
}
.nameTag span {
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
padding-inline: var(--cpd-space-2x);
}
.toolbar {
@@ -137,24 +144,6 @@ limitations under the License.
height: 16px;
}
.memberName {
left: 16px;
bottom: 16px;
}
.memberName > :last-child {
margin-right: 0px;
}
.memberName span {
font-size: var(--font-size-caption);
font-weight: 400;
line-height: var(--font-size-body);
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
.videoMutedOverlay {
width: 100%;
height: 100%;
@@ -192,12 +181,6 @@ limitations under the License.
background-color: rgba(0, 0, 0, 0.5);
}
@media (min-width: 800px) {
.videoTile {
--tileRadius: 20px;
}
}
/* CSS makes us put a condition here, even though all we want to do is
unconditionally select the container so we can use cqmin units */
@container videoTile (width > 0) {

View File

@@ -28,11 +28,12 @@ import {
RoomMember,
RoomMemberEvent,
} from "matrix-js-sdk/src/models/room-member";
import { ReactComponent as MicOnSolidIcon } from "@vector-im/compound-design-tokens/icons/mic-on-solid.svg";
import { ReactComponent as MicOffSolidIcon } from "@vector-im/compound-design-tokens/icons/mic-off-solid.svg";
import { Text } from "@vector-im/compound-web";
import { Avatar } from "../Avatar";
import styles from "./VideoTile.module.css";
import { ReactComponent as MicIcon } from "../icons/Mic.svg";
import { ReactComponent as MicMutedIcon } from "../icons/MicMuted.svg";
import { useReactiveState } from "../useReactiveState";
import { AudioButton, FullscreenButton } from "../button/Button";
import { useModalTriggerState } from "../Modal";
@@ -102,12 +103,15 @@ export const VideoTile = forwardRef<HTMLDivElement, Props>(
}
}, [member, setDisplayName]);
const { isMuted: microphoneMuted } = useMediaTrack(
content === TileContent.UserMedia
? Track.Source.Microphone
: Track.Source.ScreenShareAudio,
sfuParticipant
);
const muted =
useMediaTrack(
content === TileContent.UserMedia
? Track.Source.Microphone
: Track.Source.ScreenShareAudio,
sfuParticipant
).isMuted !== false;
const MicIcon = muted ? MicOffSolidIcon : MicOnSolidIcon;
const onFullscreen = useCallback(() => {
onToggleFullscreen(data.id);
@@ -153,7 +157,6 @@ export const VideoTile = forwardRef<HTMLDivElement, Props>(
sfuParticipant.isSpeaking &&
content === TileContent.UserMedia &&
showSpeakingIndicator,
[styles.muted]: microphoneMuted,
[styles.screenshare]: content === TileContent.ScreenShare,
[styles.maximised]: maximised,
})}
@@ -182,9 +185,16 @@ export const VideoTile = forwardRef<HTMLDivElement, Props>(
<span>{t("{{displayName}} is presenting", { displayName })}</span>
</div>
) : (
<div className={classNames(styles.infoBubble, styles.memberName)}>
{microphoneMuted === false ? <MicIcon /> : <MicMutedIcon />}
<span title={displayName}>{displayName}</span>
<div className={styles.nameTag}>
<MicIcon
width={20}
height={20}
aria-label={muted ? t("Microphone off") : t("Microphone on")}
data-muted={muted}
/>
<Text as="span" size="sm" weight="medium">
{sfuParticipant.isLocal ? t("You") : displayName}
</Text>
{showConnectionStats && (
<ConnectionQualityIndicator participant={sfuParticipant} />
)}