mirror of
https://github.com/suitenumerique/meet.git
synced 2026-07-26 11:58:53 +00:00
📦️(frontend) vendor GridLayout to use react-hook useSize
Replace livekit's modern-only useSize hook with react-hook implementation that includes polyfill support for older browsers. Will update remaining layout components in subsequent commits. Also, it now enables us to fix the pagination controls.
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
import * as React from 'react'
|
||||
import { createInteractingObservable } from '@livekit/components-core'
|
||||
import { usePagination } from '@livekit/components-react'
|
||||
import { RiArrowLeftSLine, RiArrowRightSLine } from '@remixicon/react'
|
||||
|
||||
export interface PaginationControlProps
|
||||
extends Pick<
|
||||
ReturnType<typeof usePagination>,
|
||||
'totalPageCount' | 'nextPage' | 'prevPage' | 'currentPage'
|
||||
> {
|
||||
/** Reference to an HTML element that holds the pages, while interacting (`mouseover`)
|
||||
* with it, the pagination controls will appear for a while. */
|
||||
pagesContainer?: React.RefObject<HTMLElement>
|
||||
}
|
||||
|
||||
export function PaginationControl({
|
||||
totalPageCount,
|
||||
nextPage,
|
||||
prevPage,
|
||||
currentPage,
|
||||
pagesContainer: connectedElement,
|
||||
}: PaginationControlProps) {
|
||||
const [interactive, setInteractive] = React.useState(false)
|
||||
React.useEffect(() => {
|
||||
let subscription:
|
||||
| ReturnType<ReturnType<typeof createInteractingObservable>['subscribe']>
|
||||
| undefined
|
||||
if (connectedElement) {
|
||||
subscription = createInteractingObservable(
|
||||
connectedElement.current,
|
||||
2000
|
||||
).subscribe(setInteractive)
|
||||
}
|
||||
return () => {
|
||||
if (subscription) {
|
||||
subscription.unsubscribe()
|
||||
}
|
||||
}
|
||||
}, [connectedElement])
|
||||
|
||||
return (
|
||||
<div
|
||||
className="lk-pagination-control"
|
||||
data-lk-user-interaction={interactive}
|
||||
>
|
||||
<button className="lk-button" onClick={prevPage}>
|
||||
<RiArrowLeftSLine />
|
||||
</button>
|
||||
<span className="lk-pagination-count">{`${currentPage} of ${totalPageCount}`}</span>
|
||||
<button className="lk-button" onClick={nextPage}>
|
||||
<RiArrowRightSLine />
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import * as React from 'react'
|
||||
|
||||
export interface PaginationIndicatorProps {
|
||||
totalPageCount: number
|
||||
currentPage: number
|
||||
}
|
||||
|
||||
export const PaginationIndicator: (
|
||||
props: PaginationIndicatorProps & React.RefAttributes<HTMLDivElement>
|
||||
) => React.ReactNode = /* @__PURE__ */ React.forwardRef<
|
||||
HTMLDivElement,
|
||||
PaginationIndicatorProps
|
||||
>(function PaginationIndicator(
|
||||
{ totalPageCount, currentPage }: PaginationIndicatorProps,
|
||||
ref
|
||||
) {
|
||||
const bubbles = new Array(totalPageCount).fill('').map((_, index) => {
|
||||
if (index + 1 === currentPage) {
|
||||
return <span data-lk-active key={index} />
|
||||
} else {
|
||||
return <span key={index} />
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<div ref={ref} className="lk-pagination-indicator">
|
||||
{bubbles}
|
||||
</div>
|
||||
)
|
||||
})
|
||||
@@ -0,0 +1,71 @@
|
||||
import * as React from 'react'
|
||||
import type { TrackReferenceOrPlaceholder } from '@livekit/components-core'
|
||||
import {
|
||||
TrackLoop,
|
||||
usePagination,
|
||||
UseParticipantsOptions,
|
||||
useSwipe,
|
||||
} from '@livekit/components-react'
|
||||
import { mergeProps } from '@/utils/mergeProps'
|
||||
import { PaginationIndicator } from '../controls/PaginationIndicator'
|
||||
import { useGridLayout } from '../../hooks/useGridLayout'
|
||||
import { PaginationControl } from '../controls/PaginationControl'
|
||||
|
||||
/** @public */
|
||||
export interface GridLayoutProps
|
||||
extends React.HTMLAttributes<HTMLDivElement>,
|
||||
Pick<UseParticipantsOptions, 'updateOnlyOn'> {
|
||||
children: React.ReactNode
|
||||
tracks: TrackReferenceOrPlaceholder[]
|
||||
}
|
||||
|
||||
/**
|
||||
* The `GridLayout` component displays the nested participants in a grid where every participants has the same size.
|
||||
* It also supports pagination if there are more participants than the grid can display.
|
||||
* @remarks
|
||||
* To ensure visual stability when tiles are reordered due to track updates,
|
||||
* the component uses the `useVisualStableUpdate` hook.
|
||||
* @example
|
||||
* ```tsx
|
||||
* <LiveKitRoom>
|
||||
* <GridLayout tracks={tracks}>
|
||||
* <ParticipantTile />
|
||||
* </GridLayout>
|
||||
* <LiveKitRoom>
|
||||
* ```
|
||||
* @public
|
||||
*/
|
||||
export function GridLayout({ tracks, ...props }: GridLayoutProps) {
|
||||
const gridEl = React.createRef<HTMLDivElement>()
|
||||
|
||||
const elementProps = React.useMemo(
|
||||
() => mergeProps(props, { className: 'lk-grid-layout' }),
|
||||
[props]
|
||||
)
|
||||
const { layout } = useGridLayout(gridEl, tracks.length)
|
||||
const pagination = usePagination(layout.maxTiles, tracks)
|
||||
|
||||
useSwipe(gridEl, {
|
||||
onLeftSwipe: pagination.nextPage,
|
||||
onRightSwipe: pagination.prevPage,
|
||||
})
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={gridEl}
|
||||
data-lk-pagination={pagination.totalPageCount > 1}
|
||||
{...elementProps}
|
||||
>
|
||||
<TrackLoop tracks={pagination.tracks}>{props.children}</TrackLoop>
|
||||
{tracks.length > layout.maxTiles && (
|
||||
<>
|
||||
<PaginationIndicator
|
||||
totalPageCount={pagination.totalPageCount}
|
||||
currentPage={pagination.currentPage}
|
||||
/>
|
||||
<PaginationControl pagesContainer={gridEl} {...pagination} />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import { GRID_LAYOUTS, selectGridLayout } from '@livekit/components-core'
|
||||
import type {
|
||||
GridLayoutDefinition,
|
||||
GridLayoutInfo,
|
||||
} from '@livekit/components-core'
|
||||
import * as React from 'react'
|
||||
import useSize from '@react-hook/size'
|
||||
|
||||
/**
|
||||
* The `useGridLayout` hook tries to select the best layout to fit all tiles.
|
||||
* If the available screen space is not enough, it will reduce the number of maximum visible
|
||||
* tiles and select a layout that still works visually within the given limitations.
|
||||
* As the order of tiles changes over time, the hook tries to keep visual updates to a minimum
|
||||
* while trying to display important tiles such as speaking participants or screen shares.
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* const { layout } = useGridLayout(gridElement, trackCount);
|
||||
* ```
|
||||
* @public
|
||||
*/
|
||||
export function useGridLayout(
|
||||
/** HTML element that contains the grid. */
|
||||
gridElement: React.RefObject<HTMLDivElement>,
|
||||
/** Count of tracks that should get layed out */
|
||||
trackCount: number,
|
||||
options: {
|
||||
gridLayouts?: GridLayoutDefinition[]
|
||||
} = {}
|
||||
): { layout: GridLayoutInfo; containerWidth: number; containerHeight: number } {
|
||||
const gridLayouts = options.gridLayouts ?? GRID_LAYOUTS
|
||||
const [width, height] = useSize(gridElement)
|
||||
const layout = selectGridLayout(gridLayouts, trackCount, width, height)
|
||||
|
||||
React.useEffect(() => {
|
||||
if (gridElement.current && layout) {
|
||||
gridElement.current.style.setProperty(
|
||||
'--lk-col-count',
|
||||
layout?.columns.toString()
|
||||
)
|
||||
gridElement.current.style.setProperty(
|
||||
'--lk-row-count',
|
||||
layout?.rows.toString()
|
||||
)
|
||||
}
|
||||
}, [gridElement, layout])
|
||||
|
||||
return {
|
||||
layout,
|
||||
containerWidth: width,
|
||||
containerHeight: height,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user