Skip to content

Commit

Permalink
Support referencing components via refs (#827)
Browse files Browse the repository at this point in the history
  • Loading branch information
lukasIO authored Apr 16, 2024
1 parent e55f44e commit ecef222
Show file tree
Hide file tree
Showing 27 changed files with 398 additions and 266 deletions.
5 changes: 5 additions & 0 deletions .changeset/moody-steaks-drop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@livekit/components-react": minor
---

Support referencing components via refs
2 changes: 1 addition & 1 deletion .github/workflows/size-limit.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
cache: 'pnpm'
- name: Install dependencies
run: pnpm install
- uses: andresz1/size-limit-action@35ea3c74377213d727b88532229d467a849345f4 # with pnpm support
- uses: andresz1/size-limit-action@v1.8.0
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
script: pnpm --filter @livekit/components-react exec size-limit --json
Expand Down
40 changes: 21 additions & 19 deletions packages/react/etc/components-react.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export interface AudioConferenceProps extends React_2.HTMLAttributes<HTMLDivElem
}

// @public
export function AudioTrack({ trackRef, onSubscriptionStatusChanged, volume, ...props }: AudioTrackProps): React_2.JSX.Element;
export const AudioTrack: (props: AudioTrackProps & React_2.RefAttributes<HTMLAudioElement>) => React_2.ReactElement<any, string | React_2.JSXElementConstructor<any>> | null;

// @public (undocumented)
export interface AudioTrackProps extends React_2.AudioHTMLAttributes<HTMLAudioElement> {
Expand All @@ -61,7 +61,7 @@ export interface AudioTrackProps extends React_2.AudioHTMLAttributes<HTMLAudioEl
}

// @public
export function AudioVisualizer({ trackRef, ...props }: AudioVisualizerProps): React_2.JSX.Element;
export const AudioVisualizer: (props: AudioVisualizerProps & React_2.RefAttributes<SVGSVGElement>) => React_2.ReactElement<any, string | React_2.JSXElementConstructor<any>> | null;

// @public (undocumented)
export interface AudioVisualizerProps extends React_2.HTMLAttributes<SVGElement> {
Expand Down Expand Up @@ -100,7 +100,7 @@ export function Chat({ messageFormatter, messageDecoder, messageEncoder, channel
export const ChatCloseIcon: (props: SVGProps<SVGSVGElement>) => React_2.JSX.Element;

// @public
export function ChatEntry({ entry, hideName, hideTimestamp, messageFormatter, ...props }: ChatEntryProps): React_2.JSX.Element;
export const ChatEntry: (props: ChatEntryProps & React_2.RefAttributes<HTMLLIElement>) => React_2.ReactElement<any, string | React_2.JSXElementConstructor<any>> | null;

// @public
export interface ChatEntryProps extends React_2.HTMLAttributes<HTMLLIElement> {
Expand Down Expand Up @@ -134,7 +134,7 @@ export interface ChatProps extends React_2.HTMLAttributes<HTMLDivElement>, ChatO
}

// @public
export function ChatToggle(props: ChatToggleProps): React_2.JSX.Element;
export const ChatToggle: (props: ChatToggleProps & React_2.RefAttributes<HTMLButtonElement>) => React_2.ReactElement<any, string | React_2.JSXElementConstructor<any>> | null;

// @public (undocumented)
export interface ChatToggleProps extends React_2.ButtonHTMLAttributes<HTMLButtonElement> {
Expand All @@ -146,14 +146,14 @@ export interface ChatToggleProps extends React_2.ButtonHTMLAttributes<HTMLButton
export const Chevron: (props: SVGProps<SVGSVGElement>) => React_2.JSX.Element;

// @public
export function ClearPinButton(props: ClearPinButtonProps): React_2.JSX.Element;
export const ClearPinButton: (props: ClearPinButtonProps & React_2.RefAttributes<HTMLButtonElement>) => React_2.ReactElement<any, string | React_2.JSXElementConstructor<any>> | null;

// @public (undocumented)
export interface ClearPinButtonProps extends React_2.ButtonHTMLAttributes<HTMLButtonElement> {
}

// @public
export function ConnectionQualityIndicator(props: ConnectionQualityIndicatorProps): React_2.JSX.Element;
export const ConnectionQualityIndicator: (props: ConnectionQualityIndicatorProps & React_2.RefAttributes<HTMLDivElement>) => React_2.ReactElement<any, string | React_2.JSXElementConstructor<any>> | null;

// @public (undocumented)
export interface ConnectionQualityIndicatorOptions {
Expand All @@ -166,7 +166,7 @@ export interface ConnectionQualityIndicatorProps extends React_2.HTMLAttributes<
}

// @public
export function ConnectionState({ room, ...props }: ConnectionStatusProps): React_2.JSX.Element;
export const ConnectionState: (props: ConnectionStatusProps & React_2.RefAttributes<HTMLDivElement>) => React_2.ReactElement<any, string | React_2.JSXElementConstructor<any>> | null;

// @public
export function ConnectionStateToast(props: ConnectionStateToastProps): React_2.JSX.Element;
Expand Down Expand Up @@ -206,7 +206,7 @@ export interface ControlBarProps extends React_2.HTMLAttributes<HTMLDivElement>
}

// @public
export function DisconnectButton(props: DisconnectButtonProps): React_2.JSX.Element;
export const DisconnectButton: (props: DisconnectButtonProps & React_2.RefAttributes<HTMLButtonElement>) => React_2.ReactElement<any, string | React_2.JSXElementConstructor<any>> | null;

// @public (undocumented)
export interface DisconnectButtonProps extends React_2.ButtonHTMLAttributes<HTMLButtonElement> {
Expand Down Expand Up @@ -242,7 +242,7 @@ export interface FocusLayoutProps extends React_2.HTMLAttributes<HTMLElement> {
}

// @public
export function FocusToggle({ trackRef, ...props }: FocusToggleProps): React_2.JSX.Element;
export const FocusToggle: (props: FocusToggleProps & React_2.RefAttributes<HTMLButtonElement>) => React_2.ReactElement<any, string | React_2.JSXElementConstructor<any>> | null;

// Warning: (ae-internal-missing-underscore) The name "FocusToggleIcon" should be prefixed with an underscore because the declaration is marked as @internal
//
Expand Down Expand Up @@ -307,7 +307,9 @@ export type LayoutContextType = {
export const LeaveIcon: (props: SVGProps<SVGSVGElement>) => React_2.JSX.Element;

// @public
export function LiveKitRoom(props: React_2.PropsWithChildren<LiveKitRoomProps>): React_2.JSX.Element;
export const LiveKitRoom: (props: LiveKitRoomProps & {
children?: React_2.ReactNode;
} & React_2.RefAttributes<HTMLDivElement>) => React_2.ReactElement<any, string | React_2.JSXElementConstructor<any>> | null;

// @public (undocumented)
export interface LiveKitRoomProps extends Omit<React_2.HTMLAttributes<HTMLDivElement>, 'onError'> {
Expand Down Expand Up @@ -372,7 +374,7 @@ export interface MediaDeviceMenuProps extends React_2.ButtonHTMLAttributes<HTMLB
}

// @public
export function MediaDeviceSelect({ kind, initialSelection, onActiveDeviceChange, onDeviceListChange, onDeviceSelectError, exactMatch, track, requestPermissions, onError, ...props }: MediaDeviceSelectProps): React_2.JSX.Element;
export const MediaDeviceSelect: (props: MediaDeviceSelectProps & React_2.RefAttributes<HTMLUListElement>) => React_2.ReactElement<any, string | React_2.JSXElementConstructor<any>> | null;

// @public (undocumented)
export interface MediaDeviceSelectProps extends Omit<React_2.HTMLAttributes<HTMLUListElement>, 'onError'> {
Expand Down Expand Up @@ -425,7 +427,7 @@ export interface MultiBandTrackVolumeOptions {
}

// @public
export function ParticipantAudioTile({ children, disableSpeakingIndicator, onParticipantClick, trackRef, ...htmlProps }: ParticipantTileProps): React_2.JSX.Element;
export const ParticipantAudioTile: (props: ParticipantTileProps & React_2.RefAttributes<HTMLDivElement>) => React_2.ReactElement<any, string | React_2.JSXElementConstructor<any>> | null;

// Warning: (ae-internal-missing-underscore) The name "ParticipantClickEvent" should be prefixed with an underscore because the declaration is marked as @internal
//
Expand Down Expand Up @@ -455,7 +457,7 @@ export interface ParticipantLoopProps {
}

// @public
export function ParticipantName({ participant, ...props }: ParticipantNameProps): React_2.JSX.Element;
export const ParticipantName: (props: ParticipantNameProps & React_2.RefAttributes<HTMLSpanElement>) => React_2.ReactElement<any, string | React_2.JSXElementConstructor<any>> | null;

// @public (undocumented)
export interface ParticipantNameProps extends React_2.HTMLAttributes<HTMLSpanElement>, UseParticipantInfoOptions {
Expand All @@ -467,7 +469,7 @@ export interface ParticipantNameProps extends React_2.HTMLAttributes<HTMLSpanEle
export const ParticipantPlaceholder: (props: SVGProps<SVGSVGElement>) => React_2.JSX.Element;

// @public
export function ParticipantTile({ trackRef, children, onParticipantClick, disableSpeakingIndicator, ...htmlProps }: ParticipantTileProps): React_2.JSX.Element;
export const ParticipantTile: (props: ParticipantTileProps & React_2.RefAttributes<HTMLDivElement>) => React_2.ReactElement<any, string | React_2.JSXElementConstructor<any>> | null;

// @public (undocumented)
export interface ParticipantTileProps extends React_2.HTMLAttributes<HTMLDivElement> {
Expand Down Expand Up @@ -548,7 +550,7 @@ export interface RoomAudioRendererProps {
export const RoomContext: React_2.Context<Room | undefined>;

// @public
export function RoomName({ childrenPosition, children, ...htmlAttributes }: RoomNameProps): React_2.JSX.Element;
export const RoomName: (props: RoomNameProps & React_2.RefAttributes<HTMLSpanElement>) => React_2.ReactElement<any, string | React_2.JSXElementConstructor<any>> | null;

// @public (undocumented)
export interface RoomNameProps extends React_2.HTMLAttributes<HTMLSpanElement> {
Expand Down Expand Up @@ -584,7 +586,7 @@ export function setLogLevel(level: LogLevel, options?: SetLogLevelOptions): void
export const SpinnerIcon: (props: SVGProps<SVGSVGElement>) => React_2.JSX.Element;

// @public
export function StartAudio({ label, ...props }: AllowAudioPlaybackProps): React_2.JSX.Element;
export const StartAudio: (props: AllowAudioPlaybackProps & React_2.RefAttributes<HTMLButtonElement>) => React_2.ReactElement<any, string | React_2.JSXElementConstructor<any>> | null;

// @public
export function Toast(props: React_2.HTMLAttributes<HTMLDivElement>): React_2.JSX.Element;
Expand All @@ -599,7 +601,7 @@ export interface TrackLoopProps {
}

// @public
export function TrackMutedIndicator({ trackRef, show, ...props }: TrackMutedIndicatorProps): React_2.JSX.Element | null;
export const TrackMutedIndicator: (props: TrackMutedIndicatorProps & React_2.RefAttributes<HTMLDivElement>) => React_2.ReactElement<any, string | React_2.JSXElementConstructor<any>> | null;

// @public (undocumented)
export interface TrackMutedIndicatorProps extends React_2.HTMLAttributes<HTMLDivElement> {
Expand Down Expand Up @@ -627,7 +629,7 @@ export type TrackReferenceOrPlaceholder = TrackReference | TrackReferencePlaceho
// Warning: (ae-forgotten-export) The symbol "ToggleSource" needs to be exported by the entry point index.d.ts
//
// @public
export function TrackToggle<T extends ToggleSource>({ showIcon, ...props }: TrackToggleProps<T>): React_2.JSX.Element;
export const TrackToggle: <T extends ToggleSource>(props: TrackToggleProps<T> & React_2.RefAttributes<HTMLButtonElement>) => React_2.ReactElement | null;

// @public (undocumented)
export interface TrackToggleProps<T extends ToggleSource> extends Omit<React_2.ButtonHTMLAttributes<HTMLButtonElement>, 'onChange'> {
Expand Down Expand Up @@ -1124,7 +1126,7 @@ export interface VideoConferenceProps extends React_2.HTMLAttributes<HTMLDivElem
}

// @public
export function VideoTrack({ onTrackClick, onClick, onSubscriptionStatusChanged, trackRef, manageSubscription, ...props }: VideoTrackProps): React_2.JSX.Element;
export const VideoTrack: (props: VideoTrackProps & React_2.RefAttributes<HTMLVideoElement>) => React_2.ReactElement<any, string | React_2.JSXElementConstructor<any>> | null;

// @public (undocumented)
export interface VideoTrackProps extends React_2.VideoHTMLAttributes<HTMLVideoElement> {
Expand Down
78 changes: 39 additions & 39 deletions packages/react/src/components/ChatEntry.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,48 +33,48 @@ export interface ChatEntryProps extends React.HTMLAttributes<HTMLLIElement> {
* @see `Chat`
* @public
*/
export function ChatEntry({
entry,
hideName = false,
hideTimestamp = false,
messageFormatter,
...props
}: ChatEntryProps) {
const formattedMessage = React.useMemo(() => {
return messageFormatter ? messageFormatter(entry.message) : entry.message;
}, [entry.message, messageFormatter]);
const hasBeenEdited = !!entry.editTimestamp;
const time = new Date(entry.timestamp);
const locale = navigator ? navigator.language : 'en-US';
export const ChatEntry = /* @__PURE__ */ React.forwardRef<HTMLLIElement, ChatEntryProps>(
function ChatEntry(
{ entry, hideName = false, hideTimestamp = false, messageFormatter, ...props }: ChatEntryProps,
ref,
) {
const formattedMessage = React.useMemo(() => {
return messageFormatter ? messageFormatter(entry.message) : entry.message;
}, [entry.message, messageFormatter]);
const hasBeenEdited = !!entry.editTimestamp;
const time = new Date(entry.timestamp);
const locale = navigator ? navigator.language : 'en-US';

return (
<li
className="lk-chat-entry"
title={time.toLocaleTimeString(locale, { timeStyle: 'full' })}
data-lk-message-origin={entry.from?.isLocal ? 'local' : 'remote'}
{...props}
>
{(!hideTimestamp || !hideName || hasBeenEdited) && (
<span className="lk-meta-data">
{!hideName && (
<strong className="lk-participant-name">
{entry.from?.name ?? entry.from?.identity}
</strong>
)}
return (
<li
ref={ref}
className="lk-chat-entry"
title={time.toLocaleTimeString(locale, { timeStyle: 'full' })}
data-lk-message-origin={entry.from?.isLocal ? 'local' : 'remote'}
{...props}
>
{(!hideTimestamp || !hideName || hasBeenEdited) && (
<span className="lk-meta-data">
{!hideName && (
<strong className="lk-participant-name">
{entry.from?.name ?? entry.from?.identity}
</strong>
)}

{(!hideTimestamp || hasBeenEdited) && (
<span className="lk-timestamp">
{hasBeenEdited && 'edited '}
{time.toLocaleTimeString(locale, { timeStyle: 'short' })}
</span>
)}
</span>
)}
{(!hideTimestamp || hasBeenEdited) && (
<span className="lk-timestamp">
{hasBeenEdited && 'edited '}
{time.toLocaleTimeString(locale, { timeStyle: 'short' })}
</span>
)}
</span>
)}

<span className="lk-message-body">{formattedMessage}</span>
</li>
);
}
<span className="lk-message-body">{formattedMessage}</span>
</li>
);
},
);

/** @public */
export function formatChatMessageLinks(message: string): React.ReactNode {
Expand Down
13 changes: 10 additions & 3 deletions packages/react/src/components/ConnectionState.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,14 @@ export interface ConnectionStatusProps extends React.HTMLAttributes<HTMLDivEleme
* ```
* @public
*/
export function ConnectionState({ room, ...props }: ConnectionStatusProps) {
export const ConnectionState = /* @__PURE__ */ React.forwardRef<
HTMLDivElement,
ConnectionStatusProps
>(function ConnectionState({ room, ...props }: ConnectionStatusProps, ref) {
const connectionState = useConnectionState(room);
return <div {...props}>{connectionState}</div>;
}
return (
<div ref={ref} {...props}>
{connectionState}
</div>
);
});
9 changes: 6 additions & 3 deletions packages/react/src/components/LiveKitRoom.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -100,10 +100,13 @@ export interface LiveKitRoomProps extends Omit<React.HTMLAttributes<HTMLDivEleme
* ```
* @public
*/
export function LiveKitRoom(props: React.PropsWithChildren<LiveKitRoomProps>) {
export const LiveKitRoom = /* @__PURE__ */ React.forwardRef<
HTMLDivElement,
React.PropsWithChildren<LiveKitRoomProps>
>(function LiveKitRoom(props: React.PropsWithChildren<LiveKitRoomProps>, ref) {
const { room, htmlProps } = useLiveKitRoom(props);
return (
<div {...htmlProps}>
<div ref={ref} {...htmlProps}>
{room && (
<RoomContext.Provider value={room}>
<LKFeatureContext.Provider value={props.featureFlags}>
Expand All @@ -113,4 +116,4 @@ export function LiveKitRoom(props: React.PropsWithChildren<LiveKitRoomProps>) {
)}
</div>
);
}
});
29 changes: 15 additions & 14 deletions packages/react/src/components/RoomName.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,19 @@ export interface RoomNameProps extends React.HTMLAttributes<HTMLSpanElement> {
* ```
* @public
*/
export function RoomName({
childrenPosition = 'before',
children,
...htmlAttributes
}: RoomNameProps) {
const { name } = useRoomInfo();
export const RoomName = /* @__PURE__ */ React.forwardRef<HTMLSpanElement, RoomNameProps>(
function RoomName(
{ childrenPosition = 'before', children, ...htmlAttributes }: RoomNameProps,
ref,
) {
const { name } = useRoomInfo();

return (
<span {...htmlAttributes}>
{childrenPosition === 'before' && children}
{name}
{childrenPosition === 'after' && children}
</span>
);
}
return (
<span ref={ref} {...htmlAttributes}>
{childrenPosition === 'before' && children}
{name}
{childrenPosition === 'after' && children}
</span>
);
},
);
14 changes: 10 additions & 4 deletions packages/react/src/components/controls/ChatToggle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,14 @@ export interface ChatToggleProps extends React.ButtonHTMLAttributes<HTMLButtonEl
* ```
* @public
*/
export function ChatToggle(props: ChatToggleProps) {
const { mergedProps } = useChatToggle({ props });
export const ChatToggle = /* @__PURE__ */ React.forwardRef<HTMLButtonElement, ChatToggleProps>(
function ChatToggle(props: ChatToggleProps, ref) {
const { mergedProps } = useChatToggle({ props });

return <button {...mergedProps}>{props.children}</button>;
}
return (
<button ref={ref} {...mergedProps}>
{props.children}
</button>
);
},
);
13 changes: 10 additions & 3 deletions packages/react/src/components/controls/ClearPinButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,14 @@ export interface ClearPinButtonProps extends React.ButtonHTMLAttributes<HTMLButt
* ```
* @public
*/
export function ClearPinButton(props: ClearPinButtonProps) {
export const ClearPinButton = /* @__PURE__ */ React.forwardRef<
HTMLButtonElement,
ClearPinButtonProps
>(function ClearPinButton(props: ClearPinButtonProps, ref) {
const { buttonProps } = useClearPinButton(props);
return <button {...buttonProps}>{props.children}</button>;
}
return (
<button ref={ref} {...buttonProps}>
{props.children}
</button>
);
});
Loading

0 comments on commit ecef222

Please sign in to comment.