Implement most of the remaining layout changes

Includes the mobile UX optimizations and the tweaks we've made to cut down on wasted space, but does not yet include the change to embed the spotlight tile within the grid.
This commit is contained in:
Robin
2024-07-03 15:08:30 -04:00
parent a16f235277
commit 2440037639
25 changed files with 761 additions and 497 deletions

View File

@@ -22,7 +22,7 @@ limitations under the License.
/* Use a pseudo-element to create the expressive speaking border, since CSS
borders don't support gradients */
.tile[data-maximised="false"]::before {
.tile::before {
content: "";
position: absolute;
z-index: -1; /* Put it below the outline */
@@ -43,27 +43,22 @@ borders don't support gradients */
background-blend-mode: overlay, normal;
}
.tile[data-maximised="false"].speaking {
.tile.speaking {
/* !important because speaking border should take priority over hover */
outline: var(--cpd-border-width-1) solid var(--cpd-color-bg-canvas-default) !important;
}
.tile[data-maximised="false"].speaking::before {
.tile.speaking::before {
opacity: 1;
}
@media (hover: hover) {
.tile[data-maximised="false"]:hover {
.tile:hover {
outline: var(--cpd-border-width-2) solid
var(--cpd-color-border-interactive-hovered);
}
}
.tile[data-maximised="true"] {
--media-view-border-radius: 0;
--media-view-fg-inset: 10px;
}
.muteIcon[data-muted="true"] {
color: var(--cpd-color-icon-secondary);
}

View File

@@ -57,7 +57,6 @@ interface TileProps {
style?: ComponentProps<typeof animated.div>["style"];
targetWidth: number;
targetHeight: number;
maximised: boolean;
displayName: string;
nameTag: string;
showSpeakingIndicators: boolean;
@@ -79,7 +78,6 @@ const UserMediaTile = forwardRef<HTMLDivElement, UserMediaTileProps>(
menuEnd,
className,
nameTag,
maximised,
...props
},
ref,
@@ -151,7 +149,6 @@ const UserMediaTile = forwardRef<HTMLDivElement, UserMediaTileProps>(
{menu}
</Menu>
}
data-maximised={maximised}
{...props}
/>
);
@@ -273,9 +270,6 @@ RemoteUserMediaTile.displayName = "RemoteUserMediaTile";
interface GridTileProps {
vm: UserMediaViewModel;
maximised: boolean;
fullscreen: boolean;
onToggleFullscreen: (itemId: string) => void;
onOpenProfile: () => void;
targetWidth: number;
targetHeight: number;
@@ -285,7 +279,7 @@ interface GridTileProps {
}
export const GridTile = forwardRef<HTMLDivElement, GridTileProps>(
({ vm, fullscreen, onToggleFullscreen, onOpenProfile, ...props }, ref) => {
({ vm, onOpenProfile, ...props }, ref) => {
const nameData = useNameData(vm);
if (vm instanceof LocalUserMediaViewModel) {

View File

@@ -94,7 +94,7 @@ unconditionally select the container so we can use cqmin units */
display: grid;
grid-template-columns: 1fr auto;
grid-template-rows: 1fr auto;
grid-template-areas: ". button2" "nameTag button1";
grid-template-areas: ". ." "nameTag button";
gap: var(--cpd-space-1x);
place-items: start;
}
@@ -175,9 +175,5 @@ unconditionally select the container so we can use cqmin units */
}
.fg > button:first-of-type {
grid-area: button1;
}
.fg > button:nth-of-type(2) {
grid-area: button2;
grid-area: button;
}

View File

@@ -42,7 +42,6 @@ interface Props extends ComponentProps<typeof animated.div> {
nameTag: string;
displayName: string;
primaryButton?: ReactNode;
secondaryButton?: ReactNode;
}
export const MediaView = forwardRef<HTMLDivElement, Props>(
@@ -62,7 +61,6 @@ export const MediaView = forwardRef<HTMLDivElement, Props>(
nameTag,
displayName,
primaryButton,
secondaryButton,
...props
},
ref,
@@ -120,7 +118,6 @@ export const MediaView = forwardRef<HTMLDivElement, Props>(
)}
</div>
{primaryButton}
{secondaryButton}
</div>
</animated.div>
);

View File

@@ -15,28 +15,11 @@ limitations under the License.
*/
.tile {
--border-width: var(--cpd-space-3x);
}
.tile.maximised {
--border-width: 0px;
}
.border {
box-sizing: border-box;
block-size: 100%;
inline-size: 100%;
}
.tile.maximised .border {
display: contents;
}
.contents {
display: flex;
border-radius: var(--cpd-space-6x);
contain: strict;
overflow: auto;
overflow-x: auto;
overflow-y: hidden;
scrollbar-width: none;
scroll-snap-type: inline mandatory;
scroll-snap-stop: always;
@@ -46,18 +29,18 @@ limitations under the License.
scroll-behavior: smooth; */
}
.tile.maximised .contents {
.tile.maximised {
border-radius: 0;
}
.contents > .item {
.item {
height: 100%;
flex-basis: 100%;
flex-shrink: 0;
--media-view-fg-inset: 10px;
}
.contents > .item.snap {
.item.snap {
scroll-snap-align: start;
}
@@ -105,7 +88,7 @@ limitations under the License.
inset-inline-end: var(--cpd-space-1x);
}
.fullScreen {
.expand {
appearance: none;
cursor: pointer;
opacity: 0;
@@ -118,23 +101,23 @@ limitations under the License.
transition-property: opacity, background-color;
position: absolute;
z-index: 1;
--inset: calc(var(--border-width) + 6px);
--inset: 6px;
inset-block-end: var(--inset);
inset-inline-end: var(--inset);
}
.fullScreen > svg {
.expand > svg {
display: block;
color: var(--cpd-color-icon-on-solid-primary);
}
@media (hover) {
.fullScreen:hover {
.expand:hover {
background: var(--cpd-color-bg-action-primary-hovered);
}
}
.fullScreen:active {
.expand:active {
background: var(--cpd-color-bg-action-primary-pressed);
}

View File

@@ -23,7 +23,6 @@ import {
useRef,
useState,
} from "react";
import { Glass } from "@vector-im/compound-web";
import ExpandIcon from "@vector-im/compound-design-tokens/icons/expand.svg?react";
import CollapseIcon from "@vector-im/compound-design-tokens/icons/collapse.svg?react";
import ChevronLeftIcon from "@vector-im/compound-design-tokens/icons/chevron-left.svg?react";
@@ -174,8 +173,8 @@ SpotlightItem.displayName = "SpotlightItem";
interface Props {
vms: MediaViewModel[];
maximised: boolean;
fullscreen: boolean;
onToggleFullscreen: () => void;
expanded: boolean;
onToggleExpanded: (() => void) | null;
targetWidth: number;
targetHeight: number;
showIndicators: boolean;
@@ -188,8 +187,8 @@ export const SpotlightTile = forwardRef<HTMLDivElement, Props>(
{
vms,
maximised,
fullscreen,
onToggleFullscreen,
expanded,
onToggleExpanded,
targetWidth,
targetHeight,
showIndicators,
@@ -254,9 +253,8 @@ export const SpotlightTile = forwardRef<HTMLDivElement, Props>(
setScrollToId(vms[visibleIndex + 1].id);
}, [latestVisibleId, latestVms, setScrollToId]);
const FullScreenIcon = fullscreen ? CollapseIcon : ExpandIcon;
const ToggleExpandIcon = expanded ? CollapseIcon : ExpandIcon;
// We need a wrapper element because Glass doesn't provide an animated.div
return (
<animated.div
ref={ref}
@@ -274,33 +272,29 @@ export const SpotlightTile = forwardRef<HTMLDivElement, Props>(
<ChevronLeftIcon aria-hidden width={24} height={24} />
</button>
)}
<Glass className={styles.border}>
{/* Similarly we need a wrapper element here because Glass expects a
single child */}
<div className={styles.contents}>
{vms.map((vm) => (
<SpotlightItem
key={vm.id}
vm={vm}
targetWidth={targetWidth}
targetHeight={targetHeight}
intersectionObserver={intersectionObserver}
snap={scrollToId === null || scrollToId === vm.id}
/>
))}
</div>
</Glass>
<button
className={classNames(styles.fullScreen)}
aria-label={
fullscreen
? t("video_tile.full_screen")
: t("video_tile.exit_full_screen")
}
onClick={onToggleFullscreen}
>
<FullScreenIcon aria-hidden width={20} height={20} />
</button>
{vms.map((vm) => (
<SpotlightItem
key={vm.id}
vm={vm}
targetWidth={targetWidth}
targetHeight={targetHeight}
intersectionObserver={intersectionObserver}
snap={scrollToId === null || scrollToId === vm.id}
/>
))}
{onToggleExpanded && (
<button
className={classNames(styles.expand)}
aria-label={
expanded
? t("video_tile.full_screen")
: t("video_tile.exit_full_screen")
}
onClick={onToggleExpanded}
>
<ToggleExpandIcon aria-hidden width={20} height={20} />
</button>
)}
{canGoToNext && (
<button
className={classNames(styles.advance, styles.next)}
@@ -310,15 +304,17 @@ export const SpotlightTile = forwardRef<HTMLDivElement, Props>(
<ChevronRightIcon aria-hidden width={24} height={24} />
</button>
)}
<div
className={classNames(styles.indicators, {
[styles.show]: showIndicators && vms.length > 1,
})}
>
{vms.map((vm) => (
<div className={styles.item} data-visible={vm.id === visibleId} />
))}
</div>
{!expanded && (
<div
className={classNames(styles.indicators, {
[styles.show]: showIndicators && vms.length > 1,
})}
>
{vms.map((vm) => (
<div className={styles.item} data-visible={vm.id === visibleId} />
))}
</div>
)}
</animated.div>
);
},