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:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user