💄(frontend) condition shortcut helper display based on layout

restrict shortcut helper to first tile in grid layout only
This commit is contained in:
Cyril
2026-02-05 10:32:30 +01:00
parent 9cced10c64
commit f11aaf62e8
5 changed files with 79 additions and 7 deletions
@@ -40,6 +40,7 @@ import {
import { useSnapshot } from 'valtio'
import { getShortcutById } from '@/features/shortcuts/catalog'
import { formatShortcutLabel } from '@/features/shortcuts/formatLabels'
import { participantLayoutStore } from '@/stores/participantLayout'
export function TrackRefContextIfNeeded(
props: React.PropsWithChildren<{
@@ -126,6 +127,18 @@ export const ParticipantTile: (
formatShortcutLabel(openShortcutEffective) ??
`${isMacintosh() ? '⌘' : 'Ctrl'} + /`
// Check if we should show the shortcut helper tooltip
// Only show on the first tile when in grid layout (not in carousel/left column, not in focus mode)
const { layoutType, firstGridTileTrackId } = useSnapshot(participantLayoutStore)
const currentTrackId = isTrackReference(trackReference)
? `${trackReference.participant.sid}-${trackReference.source}`
: null
const shouldShowShortcutHelper =
hasKeyboardFocus &&
layoutType === 'grid' && // Only show in grid layout
currentTrackId === firstGridTileTrackId // Only show on first tile
const interactiveProps = {
...elementProps,
// Ensure the tile is focusable to expose contextual controls to keyboard users.
@@ -245,12 +258,14 @@ export const ParticipantTile: (
)}
</ParticipantContextIfNeeded>
</TrackRefContextIfNeeded>
<ShortcutHelpTooltip
triggerLabel={t('toolbarHint', {
binding: openShortcutLabel,
})}
isVisible={hasKeyboardFocus}
/>
{shouldShowShortcutHelper && (
<ShortcutHelpTooltip
triggerLabel={t('toolbarHint', {
binding: openShortcutLabel,
})}
isVisible={hasKeyboardFocus}
/>
)}
</div>
)
})
@@ -48,7 +48,7 @@ const containerStyle = css({
const triggerStyle = css({
all: 'unset',
cursor: 'pointer',
cursor: 'default',
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
@@ -3,6 +3,7 @@ import { getScrollBarWidth } from '@livekit/components-core'
import * as React from 'react'
import { TrackLoop, useVisualStableUpdate } from '@livekit/components-react'
import { useSize } from '@/features/rooms/livekit/hooks/useResizeObserver'
import { setCarouselLayout, resetLayout } from '@/stores/participantLayout'
const MIN_HEIGHT = 130
const MIN_WIDTH = 140
@@ -79,6 +80,15 @@ export function CarouselLayout({
}
}, [maxVisibleTiles, carouselOrientation])
// Update store when carousel layout is active
React.useEffect(() => {
setCarouselLayout()
return () => {
resetLayout()
}
}, [])
return (
<aside
key={carouselOrientation}
@@ -10,6 +10,8 @@ import { mergeProps } from '@/utils/mergeProps'
import { PaginationIndicator } from '../controls/PaginationIndicator'
import { useGridLayout } from '../../hooks/useGridLayout'
import { PaginationControl } from '../controls/PaginationControl'
import { setGridLayout, resetLayout } from '@/stores/participantLayout'
import { isTrackReference } from '@livekit/components-core'
/** @public */
export interface GridLayoutProps
@@ -35,6 +37,11 @@ export interface GridLayoutProps
* ```
* @public
*/
const getTrackId = (trackRef: TrackReferenceOrPlaceholder): string | null => {
if (!isTrackReference(trackRef)) return null
return `${trackRef.participant.sid}-${trackRef.source}`
}
export function GridLayout({ tracks, ...props }: GridLayoutProps) {
const gridEl = React.createRef<HTMLDivElement>()
@@ -50,6 +57,17 @@ export function GridLayout({ tracks, ...props }: GridLayoutProps) {
onRightSwipe: pagination.prevPage,
})
// Update store when tracks change - set first tile ID for grid layout
React.useEffect(() => {
const firstTrack = pagination.tracks[0]
const firstTrackId = firstTrack ? getTrackId(firstTrack) : null
setGridLayout(firstTrackId)
return () => {
resetLayout()
}
}, [pagination.tracks])
return (
<div
ref={gridEl}
@@ -0,0 +1,29 @@
import { proxy } from 'valtio'
type LayoutType = 'grid' | 'carousel' | null
type State = {
layoutType: LayoutType
firstGridTileTrackId: string | null
}
export const participantLayoutStore = proxy<State>({
layoutType: null,
firstGridTileTrackId: null,
})
export const setGridLayout = (firstTrackId: string | null) => {
participantLayoutStore.layoutType = 'grid'
participantLayoutStore.firstGridTileTrackId = firstTrackId
}
export const setCarouselLayout = () => {
participantLayoutStore.layoutType = 'carousel'
participantLayoutStore.firstGridTileTrackId = null
}
export const resetLayout = () => {
participantLayoutStore.layoutType = null
participantLayoutStore.firstGridTileTrackId = null
}