Merge pull request #3358 from element-hq/robin/remove-forward-ref
Remove usages of forwardRef
This commit is contained in:
@@ -6,12 +6,7 @@ Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
import classNames from "classnames";
|
||||
import {
|
||||
type FC,
|
||||
type HTMLAttributes,
|
||||
type ReactNode,
|
||||
forwardRef,
|
||||
} from "react";
|
||||
import { type Ref, type FC, type HTMLAttributes, type ReactNode } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Heading, Text } from "@vector-im/compound-web";
|
||||
@@ -24,12 +19,17 @@ import { EncryptionLock } from "./room/EncryptionLock";
|
||||
import { useMediaQuery } from "./useMediaQuery";
|
||||
|
||||
interface HeaderProps extends HTMLAttributes<HTMLElement> {
|
||||
ref?: Ref<HTMLElement>;
|
||||
children: ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const Header = forwardRef<HTMLElement, HeaderProps>(
|
||||
({ children, className, ...rest }, ref) => {
|
||||
export const Header: FC<HeaderProps> = ({
|
||||
ref,
|
||||
children,
|
||||
className,
|
||||
...rest
|
||||
}) => {
|
||||
return (
|
||||
<header
|
||||
ref={ref}
|
||||
@@ -39,8 +39,7 @@ export const Header = forwardRef<HTMLElement, HeaderProps>(
|
||||
{children}
|
||||
</header>
|
||||
);
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
Header.displayName = "Header";
|
||||
|
||||
|
||||
@@ -5,11 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
import {
|
||||
type ComponentPropsWithoutRef,
|
||||
forwardRef,
|
||||
type MouseEvent,
|
||||
} from "react";
|
||||
import { type ComponentProps, type FC, type MouseEvent } from "react";
|
||||
import { Link as CpdLink } from "@vector-im/compound-web";
|
||||
import { type LinkProps, useHref, useLinkClickHandler } from "react-router-dom";
|
||||
import classNames from "classnames";
|
||||
@@ -26,31 +22,30 @@ export function useLink(
|
||||
return [href, onClick];
|
||||
}
|
||||
|
||||
type Props = Omit<
|
||||
ComponentPropsWithoutRef<typeof CpdLink>,
|
||||
"href" | "onClick"
|
||||
> & { to: LinkProps["to"]; state?: unknown };
|
||||
type Props = Omit<ComponentProps<typeof CpdLink>, "href" | "onClick"> & {
|
||||
to: LinkProps["to"];
|
||||
state?: unknown;
|
||||
};
|
||||
|
||||
/**
|
||||
* A version of Compound's link component that integrates with our router setup.
|
||||
* This is only for app-internal links.
|
||||
*/
|
||||
export const Link = forwardRef<HTMLAnchorElement, Props>(function Link(
|
||||
{ to, state, ...props },
|
||||
ref,
|
||||
) {
|
||||
export const Link: FC<Props> = ({ ref, to, state, ...props }) => {
|
||||
const [path, onClick] = useLink(to, state);
|
||||
return <CpdLink ref={ref} {...props} href={path} onClick={onClick} />;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* A link to an external web page, made to fit into blocks of text more subtly
|
||||
* than the normal Compound link component.
|
||||
*/
|
||||
export const ExternalLink = forwardRef<
|
||||
HTMLAnchorElement,
|
||||
ComponentPropsWithoutRef<"a">
|
||||
>(function ExternalLink({ className, children, ...props }, ref) {
|
||||
export const ExternalLink: FC<ComponentProps<"a">> = ({
|
||||
ref,
|
||||
className,
|
||||
children,
|
||||
...props
|
||||
}) => {
|
||||
return (
|
||||
<a
|
||||
ref={ref}
|
||||
@@ -62,4 +57,4 @@ export const ExternalLink = forwardRef<
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -5,24 +5,22 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { type ComponentPropsWithoutRef, forwardRef } from "react";
|
||||
import { type ComponentProps, type FC } from "react";
|
||||
import { Button } from "@vector-im/compound-web";
|
||||
|
||||
import type { LinkProps } from "react-router-dom";
|
||||
import { useLink } from "./Link";
|
||||
|
||||
type Props = Omit<
|
||||
ComponentPropsWithoutRef<typeof Button<"a">>,
|
||||
"as" | "href"
|
||||
> & { to: LinkProps["to"]; state?: unknown };
|
||||
type Props = Omit<ComponentProps<typeof Button<"a">>, "as" | "href"> & {
|
||||
to: LinkProps["to"];
|
||||
state?: unknown;
|
||||
};
|
||||
|
||||
/**
|
||||
* A version of Compound's button component that acts as a link and integrates
|
||||
* with our router setup.
|
||||
*/
|
||||
export const LinkButton = forwardRef<HTMLAnchorElement, Props>(
|
||||
function LinkButton({ to, state, ...props }, ref) {
|
||||
export const LinkButton: FC<Props> = ({ ref, to, state, ...props }) => {
|
||||
const [path, onClick] = useLink(to, state);
|
||||
return <Button as="a" ref={ref} {...props} href={path} onClick={onClick} />;
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
@@ -6,18 +6,23 @@ Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
import classNames from "classnames";
|
||||
import { type FormEventHandler, forwardRef, type ReactNode } from "react";
|
||||
import {
|
||||
type FC,
|
||||
type Ref,
|
||||
type FormEventHandler,
|
||||
type ReactNode,
|
||||
} from "react";
|
||||
|
||||
import styles from "./Form.module.css";
|
||||
|
||||
interface FormProps {
|
||||
ref?: Ref<HTMLFormElement>;
|
||||
className: string;
|
||||
onSubmit: FormEventHandler<HTMLFormElement>;
|
||||
children: ReactNode[];
|
||||
}
|
||||
|
||||
export const Form = forwardRef<HTMLFormElement, FormProps>(
|
||||
({ children, className, onSubmit }, ref) => {
|
||||
export const Form: FC<FormProps> = ({ ref, children, className, onSubmit }) => {
|
||||
return (
|
||||
<form
|
||||
onSubmit={onSubmit}
|
||||
@@ -27,7 +32,6 @@ export const Form = forwardRef<HTMLFormElement, FormProps>(
|
||||
{children}
|
||||
</form>
|
||||
);
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
Form.displayName = "Form";
|
||||
|
||||
@@ -18,11 +18,10 @@ import {
|
||||
type ComponentType,
|
||||
type Dispatch,
|
||||
type FC,
|
||||
type LegacyRef,
|
||||
type ReactNode,
|
||||
type Ref,
|
||||
type SetStateAction,
|
||||
createContext,
|
||||
forwardRef,
|
||||
memo,
|
||||
use,
|
||||
useEffect,
|
||||
@@ -162,7 +161,7 @@ const windowHeightObservable$ = fromEvent(window, "resize").pipe(
|
||||
);
|
||||
|
||||
export interface LayoutProps<LayoutModel, TileModel, R extends HTMLElement> {
|
||||
ref: LegacyRef<R>;
|
||||
ref?: Ref<R>;
|
||||
model: LayoutModel;
|
||||
/**
|
||||
* Component creating an invisible "slot" for a tile to go in.
|
||||
@@ -171,7 +170,7 @@ export interface LayoutProps<LayoutModel, TileModel, R extends HTMLElement> {
|
||||
}
|
||||
|
||||
export interface TileProps<Model, R extends HTMLElement> {
|
||||
ref: LegacyRef<R>;
|
||||
ref?: Ref<R>;
|
||||
className?: string;
|
||||
style?: ComponentProps<typeof animated.div>["style"];
|
||||
/**
|
||||
@@ -297,14 +296,13 @@ export function Grid<
|
||||
// render of Grid causes a re-render of Layout, which in turn re-renders Grid
|
||||
const LayoutMemo = useMemo(
|
||||
() =>
|
||||
memo(
|
||||
forwardRef<
|
||||
LayoutRef,
|
||||
LayoutMemoProps<LayoutModel, TileModel, LayoutRef>
|
||||
>(function LayoutMemo({ Layout, ...props }, ref): ReactNode {
|
||||
memo(function LayoutMemo({
|
||||
ref,
|
||||
Layout,
|
||||
...props
|
||||
}: LayoutMemoProps<LayoutModel, TileModel, LayoutRef>): ReactNode {
|
||||
return <Layout {...props} ref={ref} />;
|
||||
}),
|
||||
),
|
||||
[],
|
||||
);
|
||||
|
||||
|
||||
@@ -5,7 +5,12 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { type CSSProperties, forwardRef, useCallback, useMemo } from "react";
|
||||
import {
|
||||
type CSSProperties,
|
||||
type ReactNode,
|
||||
useCallback,
|
||||
useMemo,
|
||||
} from "react";
|
||||
import { distinctUntilChanged } from "rxjs";
|
||||
import { useObservableEagerState } from "observable-hooks";
|
||||
|
||||
@@ -33,7 +38,7 @@ export const makeGridLayout: CallLayout<GridLayoutModel> = ({
|
||||
|
||||
// The "fixed" (non-scrolling) part of the layout is where the spotlight tile
|
||||
// lives
|
||||
fixed: forwardRef(function GridLayoutFixed({ model, Slot }, ref) {
|
||||
fixed: function GridLayoutFixed({ ref, model, Slot }): ReactNode {
|
||||
useUpdateLayout();
|
||||
const alignment = useObservableEagerState(
|
||||
useInitial(() =>
|
||||
@@ -68,10 +73,10 @@ export const makeGridLayout: CallLayout<GridLayoutModel> = ({
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}),
|
||||
},
|
||||
|
||||
// The scrolling part of the layout is where all the grid tiles live
|
||||
scrolling: forwardRef(function GridLayout({ model, Slot }, ref) {
|
||||
scrolling: function GridLayout({ ref, model, Slot }): ReactNode {
|
||||
useUpdateLayout();
|
||||
useVisibleTiles(model.setVisibleTiles);
|
||||
const { width, height: minHeight } = useObservableEagerState(minBounds$);
|
||||
@@ -98,5 +103,5 @@ export const makeGridLayout: CallLayout<GridLayoutModel> = ({
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { forwardRef, useCallback, useMemo } from "react";
|
||||
import { type ReactNode, useCallback, useMemo } from "react";
|
||||
import { useObservableEagerState } from "observable-hooks";
|
||||
import classNames from "classnames";
|
||||
|
||||
@@ -24,12 +24,12 @@ export const makeOneOnOneLayout: CallLayout<OneOnOneLayoutModel> = ({
|
||||
}) => ({
|
||||
scrollingOnTop: false,
|
||||
|
||||
fixed: forwardRef(function OneOnOneLayoutFixed(_props, ref) {
|
||||
fixed: function OneOnOneLayoutFixed({ ref }): ReactNode {
|
||||
useUpdateLayout();
|
||||
return <div ref={ref} />;
|
||||
}),
|
||||
},
|
||||
|
||||
scrolling: forwardRef(function OneOnOneLayoutScrolling({ model, Slot }, ref) {
|
||||
scrolling: function OneOnOneLayoutScrolling({ ref, model, Slot }): ReactNode {
|
||||
useUpdateLayout();
|
||||
const { width, height } = useObservableEagerState(minBounds$);
|
||||
const pipAlignmentValue = useObservableEagerState(pipAlignment$);
|
||||
@@ -66,5 +66,5 @@ export const makeOneOnOneLayout: CallLayout<OneOnOneLayoutModel> = ({
|
||||
</Slot>
|
||||
</div>
|
||||
);
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { forwardRef, useCallback } from "react";
|
||||
import { type ReactNode, useCallback } from "react";
|
||||
import { useObservableEagerState } from "observable-hooks";
|
||||
|
||||
import { type SpotlightExpandedLayout as SpotlightExpandedLayoutModel } from "../state/CallViewModel";
|
||||
@@ -22,10 +22,11 @@ export const makeSpotlightExpandedLayout: CallLayout<
|
||||
> = ({ pipAlignment$ }) => ({
|
||||
scrollingOnTop: true,
|
||||
|
||||
fixed: forwardRef(function SpotlightExpandedLayoutFixed(
|
||||
{ model, Slot },
|
||||
fixed: function SpotlightExpandedLayoutFixed({
|
||||
ref,
|
||||
) {
|
||||
model,
|
||||
Slot,
|
||||
}): ReactNode {
|
||||
useUpdateLayout();
|
||||
|
||||
return (
|
||||
@@ -37,12 +38,13 @@ export const makeSpotlightExpandedLayout: CallLayout<
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}),
|
||||
},
|
||||
|
||||
scrolling: forwardRef(function SpotlightExpandedLayoutScrolling(
|
||||
{ model, Slot },
|
||||
scrolling: function SpotlightExpandedLayoutScrolling({
|
||||
ref,
|
||||
) {
|
||||
model,
|
||||
Slot,
|
||||
}): ReactNode {
|
||||
useUpdateLayout();
|
||||
const pipAlignmentValue = useObservableEagerState(pipAlignment$);
|
||||
|
||||
@@ -69,5 +71,5 @@ export const makeSpotlightExpandedLayout: CallLayout<
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { forwardRef } from "react";
|
||||
import { type ReactNode } from "react";
|
||||
import { useObservableEagerState } from "observable-hooks";
|
||||
import classNames from "classnames";
|
||||
|
||||
@@ -24,10 +24,11 @@ export const makeSpotlightLandscapeLayout: CallLayout<
|
||||
> = ({ minBounds$ }) => ({
|
||||
scrollingOnTop: false,
|
||||
|
||||
fixed: forwardRef(function SpotlightLandscapeLayoutFixed(
|
||||
{ model, Slot },
|
||||
fixed: function SpotlightLandscapeLayoutFixed({
|
||||
ref,
|
||||
) {
|
||||
model,
|
||||
Slot,
|
||||
}): ReactNode {
|
||||
useUpdateLayout();
|
||||
useObservableEagerState(minBounds$);
|
||||
|
||||
@@ -43,12 +44,13 @@ export const makeSpotlightLandscapeLayout: CallLayout<
|
||||
<div className={styles.grid} />
|
||||
</div>
|
||||
);
|
||||
}),
|
||||
},
|
||||
|
||||
scrolling: forwardRef(function SpotlightLandscapeLayoutScrolling(
|
||||
{ model, Slot },
|
||||
scrolling: function SpotlightLandscapeLayoutScrolling({
|
||||
ref,
|
||||
) {
|
||||
model,
|
||||
Slot,
|
||||
}): ReactNode {
|
||||
useUpdateLayout();
|
||||
useVisibleTiles(model.setVisibleTiles);
|
||||
useObservableEagerState(minBounds$);
|
||||
@@ -69,5 +71,5 @@ export const makeSpotlightLandscapeLayout: CallLayout<
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { type CSSProperties, forwardRef } from "react";
|
||||
import { type ReactNode, type CSSProperties } from "react";
|
||||
import { useObservableEagerState } from "observable-hooks";
|
||||
import classNames from "classnames";
|
||||
|
||||
@@ -30,10 +30,11 @@ export const makeSpotlightPortraitLayout: CallLayout<
|
||||
> = ({ minBounds$ }) => ({
|
||||
scrollingOnTop: false,
|
||||
|
||||
fixed: forwardRef(function SpotlightPortraitLayoutFixed(
|
||||
{ model, Slot },
|
||||
fixed: function SpotlightPortraitLayoutFixed({
|
||||
ref,
|
||||
) {
|
||||
model,
|
||||
Slot,
|
||||
}): ReactNode {
|
||||
useUpdateLayout();
|
||||
|
||||
return (
|
||||
@@ -47,12 +48,13 @@ export const makeSpotlightPortraitLayout: CallLayout<
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}),
|
||||
},
|
||||
|
||||
scrolling: forwardRef(function SpotlightPortraitLayoutScrolling(
|
||||
{ model, Slot },
|
||||
scrolling: function SpotlightPortraitLayoutScrolling({
|
||||
ref,
|
||||
) {
|
||||
model,
|
||||
Slot,
|
||||
}): ReactNode {
|
||||
useUpdateLayout();
|
||||
useVisibleTiles(model.setVisibleTiles);
|
||||
const { width } = useObservableEagerState(minBounds$);
|
||||
@@ -90,5 +92,5 @@ export const makeSpotlightPortraitLayout: CallLayout<
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -9,10 +9,10 @@ import {
|
||||
type ChangeEvent,
|
||||
type FC,
|
||||
type ForwardedRef,
|
||||
forwardRef,
|
||||
type ReactNode,
|
||||
useId,
|
||||
type JSX,
|
||||
type Ref,
|
||||
} from "react";
|
||||
import classNames from "classnames";
|
||||
|
||||
@@ -54,6 +54,7 @@ function Field({ children, className }: FieldProps): JSX.Element {
|
||||
}
|
||||
|
||||
interface InputFieldProps {
|
||||
ref?: Ref<HTMLInputElement | HTMLTextAreaElement>;
|
||||
label?: string;
|
||||
type: string;
|
||||
prefix?: string;
|
||||
@@ -78,12 +79,8 @@ interface InputFieldProps {
|
||||
onChange?: (event: ChangeEvent<HTMLInputElement>) => void;
|
||||
}
|
||||
|
||||
export const InputField = forwardRef<
|
||||
HTMLInputElement | HTMLTextAreaElement,
|
||||
InputFieldProps
|
||||
>(
|
||||
(
|
||||
{
|
||||
export const InputField: FC<InputFieldProps> = ({
|
||||
ref,
|
||||
id,
|
||||
label,
|
||||
className,
|
||||
@@ -95,9 +92,7 @@ export const InputField = forwardRef<
|
||||
disabled,
|
||||
min,
|
||||
...rest
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
}) => {
|
||||
const descriptionId = useId();
|
||||
|
||||
return (
|
||||
@@ -158,8 +153,7 @@ export const InputField = forwardRef<
|
||||
)}
|
||||
</Field>
|
||||
);
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
InputField.displayName = "InputField";
|
||||
|
||||
|
||||
@@ -12,15 +12,14 @@ import { type MatrixClient } from "matrix-js-sdk";
|
||||
import {
|
||||
type FC,
|
||||
type PointerEvent,
|
||||
type PropsWithoutRef,
|
||||
type TouchEvent,
|
||||
forwardRef,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
type JSX,
|
||||
type ReactNode,
|
||||
} from "react";
|
||||
import useMeasure from "react-use-measure";
|
||||
import { type MatrixRTCSession } from "matrix-js-sdk/lib/matrixrtc";
|
||||
@@ -437,13 +436,14 @@ export const InCallView: FC<InCallViewProps> = ({
|
||||
|
||||
const Tile = useMemo(
|
||||
() =>
|
||||
forwardRef<
|
||||
HTMLDivElement,
|
||||
PropsWithoutRef<TileProps<TileViewModel, HTMLDivElement>>
|
||||
>(function Tile(
|
||||
{ className, style, targetWidth, targetHeight, model },
|
||||
function Tile({
|
||||
ref,
|
||||
) {
|
||||
className,
|
||||
style,
|
||||
targetWidth,
|
||||
targetHeight,
|
||||
model,
|
||||
}: TileProps<TileViewModel, HTMLDivElement>): ReactNode {
|
||||
const spotlightExpanded = useObservableEagerState(
|
||||
vm.spotlightExpanded$,
|
||||
);
|
||||
@@ -481,7 +481,7 @@ export const InCallView: FC<InCallViewProps> = ({
|
||||
style={style}
|
||||
/>
|
||||
);
|
||||
}),
|
||||
},
|
||||
[vm, openProfile],
|
||||
);
|
||||
|
||||
|
||||
@@ -7,8 +7,9 @@ Please see LICENSE in the repository root for full details.
|
||||
|
||||
import {
|
||||
type ComponentProps,
|
||||
type FC,
|
||||
type ReactNode,
|
||||
forwardRef,
|
||||
type Ref,
|
||||
useCallback,
|
||||
useRef,
|
||||
useState,
|
||||
@@ -50,6 +51,7 @@ import { useMergedRefs } from "../useMergedRefs";
|
||||
import { useReactionsSender } from "../reactions/useReactionsSender";
|
||||
|
||||
interface TileProps {
|
||||
ref?: Ref<HTMLDivElement>;
|
||||
className?: string;
|
||||
style?: ComponentProps<typeof animated.div>["style"];
|
||||
targetWidth: number;
|
||||
@@ -66,9 +68,8 @@ interface UserMediaTileProps extends TileProps {
|
||||
menuEnd?: ReactNode;
|
||||
}
|
||||
|
||||
const UserMediaTile = forwardRef<HTMLDivElement, UserMediaTileProps>(
|
||||
(
|
||||
{
|
||||
const UserMediaTile: FC<UserMediaTileProps> = ({
|
||||
ref,
|
||||
vm,
|
||||
showSpeakingIndicators,
|
||||
locallyMuted,
|
||||
@@ -77,9 +78,7 @@ const UserMediaTile = forwardRef<HTMLDivElement, UserMediaTileProps>(
|
||||
className,
|
||||
displayName,
|
||||
...props
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
}) => {
|
||||
const { toggleRaisedHand } = useReactionsSender();
|
||||
const { t } = useTranslation();
|
||||
const video = useObservableEagerState(vm.video$);
|
||||
@@ -190,8 +189,7 @@ const UserMediaTile = forwardRef<HTMLDivElement, UserMediaTileProps>(
|
||||
{menu}
|
||||
</ContextMenu>
|
||||
);
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
UserMediaTile.displayName = "UserMediaTile";
|
||||
|
||||
@@ -200,8 +198,12 @@ interface LocalUserMediaTileProps extends TileProps {
|
||||
onOpenProfile: (() => void) | null;
|
||||
}
|
||||
|
||||
const LocalUserMediaTile = forwardRef<HTMLDivElement, LocalUserMediaTileProps>(
|
||||
({ vm, onOpenProfile, ...props }, ref) => {
|
||||
const LocalUserMediaTile: FC<LocalUserMediaTileProps> = ({
|
||||
ref,
|
||||
vm,
|
||||
onOpenProfile,
|
||||
...props
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const mirror = useObservableEagerState(vm.mirror$);
|
||||
const alwaysShow = useObservableEagerState(vm.alwaysShow$);
|
||||
@@ -240,8 +242,7 @@ const LocalUserMediaTile = forwardRef<HTMLDivElement, LocalUserMediaTileProps>(
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
LocalUserMediaTile.displayName = "LocalUserMediaTile";
|
||||
|
||||
@@ -249,10 +250,11 @@ interface RemoteUserMediaTileProps extends TileProps {
|
||||
vm: RemoteUserMediaViewModel;
|
||||
}
|
||||
|
||||
const RemoteUserMediaTile = forwardRef<
|
||||
HTMLDivElement,
|
||||
RemoteUserMediaTileProps
|
||||
>(({ vm, ...props }, ref) => {
|
||||
const RemoteUserMediaTile: FC<RemoteUserMediaTileProps> = ({
|
||||
ref,
|
||||
vm,
|
||||
...props
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const locallyMuted = useObservableEagerState(vm.locallyMuted$);
|
||||
const localVolume = useObservableEagerState(vm.localVolume$);
|
||||
@@ -303,11 +305,12 @@ const RemoteUserMediaTile = forwardRef<
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
RemoteUserMediaTile.displayName = "RemoteUserMediaTile";
|
||||
|
||||
interface GridTileProps {
|
||||
ref?: Ref<HTMLDivElement>;
|
||||
vm: GridTileViewModel;
|
||||
onOpenProfile: (() => void) | null;
|
||||
targetWidth: number;
|
||||
@@ -317,8 +320,12 @@ interface GridTileProps {
|
||||
showSpeakingIndicators: boolean;
|
||||
}
|
||||
|
||||
export const GridTile = forwardRef<HTMLDivElement, GridTileProps>(
|
||||
({ vm, onOpenProfile, ...props }, theirRef) => {
|
||||
export const GridTile: FC<GridTileProps> = ({
|
||||
ref: theirRef,
|
||||
vm,
|
||||
onOpenProfile,
|
||||
...props
|
||||
}) => {
|
||||
const ourRef = useRef<HTMLDivElement | null>(null);
|
||||
const ref = useMergedRefs(ourRef, theirRef);
|
||||
const media = useObservableEagerState(vm.media$);
|
||||
@@ -344,7 +351,6 @@ export const GridTile = forwardRef<HTMLDivElement, GridTileProps>(
|
||||
/>
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
GridTile.displayName = "GridTile";
|
||||
|
||||
@@ -8,7 +8,7 @@ Please see LICENSE in the repository root for full details.
|
||||
import { type TrackReferenceOrPlaceholder } from "@livekit/components-core";
|
||||
import { animated } from "@react-spring/web";
|
||||
import { type RoomMember } from "matrix-js-sdk";
|
||||
import { type ComponentProps, type ReactNode, forwardRef } from "react";
|
||||
import { type FC, type ComponentProps, type ReactNode } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import classNames from "classnames";
|
||||
import { VideoTrack } from "@livekit/components-react";
|
||||
@@ -47,9 +47,8 @@ interface Props extends ComponentProps<typeof animated.div> {
|
||||
videoStreamStats?: RTCInboundRtpStreamStats | RTCOutboundRtpStreamStats;
|
||||
}
|
||||
|
||||
export const MediaView = forwardRef<HTMLDivElement, Props>(
|
||||
(
|
||||
{
|
||||
export const MediaView: FC<Props> = ({
|
||||
ref,
|
||||
className,
|
||||
style,
|
||||
targetWidth,
|
||||
@@ -71,9 +70,7 @@ export const MediaView = forwardRef<HTMLDivElement, Props>(
|
||||
audioStreamStats,
|
||||
videoStreamStats,
|
||||
...props
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [handRaiseTimerVisible] = useSetting(showHandRaisedTimer);
|
||||
|
||||
@@ -182,7 +179,6 @@ export const MediaView = forwardRef<HTMLDivElement, Props>(
|
||||
</div>
|
||||
</animated.div>
|
||||
);
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
MediaView.displayName = "MediaView";
|
||||
|
||||
@@ -7,8 +7,9 @@ Please see LICENSE in the repository root for full details.
|
||||
|
||||
import {
|
||||
type ComponentProps,
|
||||
type FC,
|
||||
type Ref,
|
||||
type RefAttributes,
|
||||
forwardRef,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useRef,
|
||||
@@ -44,6 +45,7 @@ import { useLatest } from "../useLatest";
|
||||
import { type SpotlightTileViewModel } from "../state/TileViewModel";
|
||||
|
||||
interface SpotlightItemBaseProps {
|
||||
ref?: Ref<HTMLDivElement>;
|
||||
className?: string;
|
||||
"data-id": string;
|
||||
targetWidth: number;
|
||||
@@ -67,13 +69,13 @@ interface SpotlightLocalUserMediaItemProps
|
||||
vm: LocalUserMediaViewModel;
|
||||
}
|
||||
|
||||
const SpotlightLocalUserMediaItem = forwardRef<
|
||||
HTMLDivElement,
|
||||
SpotlightLocalUserMediaItemProps
|
||||
>(({ vm, ...props }, ref) => {
|
||||
const SpotlightLocalUserMediaItem: FC<SpotlightLocalUserMediaItemProps> = ({
|
||||
vm,
|
||||
...props
|
||||
}) => {
|
||||
const mirror = useObservableEagerState(vm.mirror$);
|
||||
return <MediaView ref={ref} mirror={mirror} {...props} />;
|
||||
});
|
||||
return <MediaView mirror={mirror} {...props} />;
|
||||
};
|
||||
|
||||
SpotlightLocalUserMediaItem.displayName = "SpotlightLocalUserMediaItem";
|
||||
|
||||
@@ -81,16 +83,15 @@ interface SpotlightUserMediaItemProps extends SpotlightItemBaseProps {
|
||||
vm: UserMediaViewModel;
|
||||
}
|
||||
|
||||
const SpotlightUserMediaItem = forwardRef<
|
||||
HTMLDivElement,
|
||||
SpotlightUserMediaItemProps
|
||||
>(({ vm, ...props }, ref) => {
|
||||
const SpotlightUserMediaItem: FC<SpotlightUserMediaItemProps> = ({
|
||||
vm,
|
||||
...props
|
||||
}) => {
|
||||
const videoEnabled = useObservableEagerState(vm.videoEnabled$);
|
||||
const cropVideo = useObservableEagerState(vm.cropVideo$);
|
||||
|
||||
const baseProps: SpotlightUserMediaItemBaseProps &
|
||||
RefAttributes<HTMLDivElement> = {
|
||||
ref,
|
||||
videoEnabled,
|
||||
videoFit: cropVideo ? "cover" : "contain",
|
||||
...props,
|
||||
@@ -101,11 +102,12 @@ const SpotlightUserMediaItem = forwardRef<
|
||||
) : (
|
||||
<MediaView mirror={false} {...baseProps} />
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
SpotlightUserMediaItem.displayName = "SpotlightUserMediaItem";
|
||||
|
||||
interface SpotlightItemProps {
|
||||
ref?: Ref<HTMLDivElement>;
|
||||
vm: MediaViewModel;
|
||||
targetWidth: number;
|
||||
targetHeight: number;
|
||||
@@ -117,18 +119,15 @@ interface SpotlightItemProps {
|
||||
"aria-hidden"?: boolean;
|
||||
}
|
||||
|
||||
const SpotlightItem = forwardRef<HTMLDivElement, SpotlightItemProps>(
|
||||
(
|
||||
{
|
||||
const SpotlightItem: FC<SpotlightItemProps> = ({
|
||||
ref: theirRef,
|
||||
vm,
|
||||
targetWidth,
|
||||
targetHeight,
|
||||
intersectionObserver$,
|
||||
snap,
|
||||
"aria-hidden": ariaHidden,
|
||||
},
|
||||
theirRef,
|
||||
) => {
|
||||
}) => {
|
||||
const ourRef = useRef<HTMLDivElement | null>(null);
|
||||
const ref = useMergedRefs(ourRef, theirRef);
|
||||
const displayName = useObservableEagerState(vm.displayname$);
|
||||
@@ -167,21 +166,16 @@ const SpotlightItem = forwardRef<HTMLDivElement, SpotlightItemProps>(
|
||||
};
|
||||
|
||||
return vm instanceof ScreenShareViewModel ? (
|
||||
<MediaView
|
||||
videoEnabled
|
||||
videoFit="contain"
|
||||
mirror={false}
|
||||
{...baseProps}
|
||||
/>
|
||||
<MediaView videoEnabled videoFit="contain" mirror={false} {...baseProps} />
|
||||
) : (
|
||||
<SpotlightUserMediaItem vm={vm} {...baseProps} />
|
||||
);
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
SpotlightItem.displayName = "SpotlightItem";
|
||||
|
||||
interface Props {
|
||||
ref?: Ref<HTMLDivElement>;
|
||||
vm: SpotlightTileViewModel;
|
||||
expanded: boolean;
|
||||
onToggleExpanded: (() => void) | null;
|
||||
@@ -192,9 +186,8 @@ interface Props {
|
||||
style?: ComponentProps<typeof animated.div>["style"];
|
||||
}
|
||||
|
||||
export const SpotlightTile = forwardRef<HTMLDivElement, Props>(
|
||||
(
|
||||
{
|
||||
export const SpotlightTile: FC<Props> = ({
|
||||
ref: theirRef,
|
||||
vm,
|
||||
expanded,
|
||||
onToggleExpanded,
|
||||
@@ -203,17 +196,13 @@ export const SpotlightTile = forwardRef<HTMLDivElement, Props>(
|
||||
showIndicators,
|
||||
className,
|
||||
style,
|
||||
},
|
||||
theirRef,
|
||||
) => {
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [ourRef, root$] = useObservableRef<HTMLDivElement | null>(null);
|
||||
const ref = useMergedRefs(ourRef, theirRef);
|
||||
const maximised = useObservableEagerState(vm.maximised$);
|
||||
const media = useObservableEagerState(vm.media$);
|
||||
const [visibleId, setVisibleId] = useState<string | undefined>(
|
||||
media[0]?.id,
|
||||
);
|
||||
const [visibleId, setVisibleId] = useState<string | undefined>(media[0]?.id);
|
||||
const latestMedia = useLatest(media);
|
||||
const latestVisibleId = useLatest(visibleId);
|
||||
const visibleIndex = media.findIndex((vm) => vm.id === visibleId);
|
||||
@@ -243,9 +232,7 @@ export const SpotlightTile = forwardRef<HTMLDivElement, Props>(
|
||||
|
||||
const [scrollToId, setScrollToId] = useReactiveState<string | null>(
|
||||
(prev) =>
|
||||
prev == null ||
|
||||
prev === visibleId ||
|
||||
media.every((vm) => vm.id !== prev)
|
||||
prev == null || prev === visibleId || media.every((vm) => vm.id !== prev)
|
||||
? null
|
||||
: prev,
|
||||
[visibleId],
|
||||
@@ -341,7 +328,6 @@ export const SpotlightTile = forwardRef<HTMLDivElement, Props>(
|
||||
)}
|
||||
</animated.div>
|
||||
);
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
SpotlightTile.displayName = "SpotlightTile";
|
||||
|
||||
@@ -5,21 +5,21 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { type MutableRefObject, type RefCallback, useCallback } from "react";
|
||||
import { type RefCallback, type RefObject, useCallback } from "react";
|
||||
|
||||
/**
|
||||
* Combines multiple refs into one, useful for attaching multiple refs to the
|
||||
* same DOM node.
|
||||
*/
|
||||
export const useMergedRefs = <T>(
|
||||
...refs: (MutableRefObject<T | null> | RefCallback<T | null> | null)[]
|
||||
...refs: (RefObject<T | null> | RefCallback<T | null> | null | undefined)[]
|
||||
): RefCallback<T | null> =>
|
||||
useCallback(
|
||||
(value) =>
|
||||
refs.forEach((ref) => {
|
||||
if (typeof ref === "function") {
|
||||
ref(value);
|
||||
} else if (ref !== null) {
|
||||
} else if (ref) {
|
||||
ref.current = value;
|
||||
}
|
||||
}),
|
||||
|
||||
Reference in New Issue
Block a user