(frontend) make reaction toolbar responsive on small viewports

Add horizontal navigation support using left/right arrow controls
to scroll through available reactions on smaller screens.

This work is inspired by Cyril's implementation.
This commit is contained in:
lebaudantoine
2026-05-18 16:16:41 +02:00
committed by aleb_the_flash
parent ecf5d443d6
commit b4ced74b1f
2 changed files with 74 additions and 9 deletions
+2 -1
View File
@@ -10,8 +10,9 @@ and this project adheres to
### Added
- ✨(fullstack) allow participants to mute others based on room configuration
- ✨(fullstack) allow participants to mute others based on room configuration
- ✨(frontend) add synchronizer for room metadata updates
- ✨(frontend) make reaction toolbar responsive on small viewports
### Changed
@@ -1,15 +1,20 @@
import { useEffect, useRef, useState } from 'react'
import { useCallback, useEffect, useRef, useState } from 'react'
import { styled } from '@/styled-system/jsx'
import { useReactionsToolbar } from '../../hooks/useReactionsToolbar'
import { useIsMobile } from '@/utils/useIsMobile'
import { css } from '@/styled-system/css'
import { useSize } from '@/features/rooms/livekit/hooks/useResizeObserver'
import { RiArrowLeftSLine, RiArrowRightSLine } from '@remixicon/react'
import { Button } from '@/primitives'
const StyledContainer = styled('div', {
base: {
display: 'flex',
alignItems: 'center',
gap: '0.2rem',
borderRadius: '21px',
padding: '0.15rem',
backgroundColor: 'primaryDark.100',
maxWidth: '100%',
opacity: 0,
transform: 'translateY(3.25rem)',
transition: 'opacity, transform',
@@ -37,6 +42,23 @@ const StyledContainer = styled('div', {
},
})
const scrollViewport = css({
display: 'flex',
gap: '0.2rem',
overflowX: 'auto',
padding: '0.19rem',
scrollBehavior: 'smooth',
minWidth: 0,
flex: '1 1 auto',
scrollbarWidth: 'none',
'&::-webkit-scrollbar': { display: 'none' },
'& > *': {
flexShrink: 0,
},
})
const SCROLL_AMOUNT = 120 // roughly 3 buttons
export const ReactionButtonsContainer = ({
children,
}: {
@@ -44,28 +66,70 @@ export const ReactionButtonsContainer = ({
}) => {
const { isOpen } = useReactionsToolbar()
const isMobile = useIsMobile()
const ref = useRef<HTMLDivElement>(null)
const scrollRef = useRef<HTMLDivElement>(null)
const { width } = useSize(scrollRef)
const [isVisible, setIsVisible] = useState(false)
const [overflowing, setOverflowing] = useState(false)
const [atStart, setAtStart] = useState(true)
const [atEnd, setAtEnd] = useState(false)
const updateArrows = useCallback(() => {
const el = scrollRef.current
if (!el) return
setOverflowing(el.scrollWidth > el.clientWidth + 1)
setAtStart(el.scrollLeft <= 0)
setAtEnd(el.scrollLeft + el.clientWidth >= el.scrollWidth - 1)
}, [])
useEffect(() => {
if (isOpen) {
// defer one frame so the browser paints opacity:0 first
const id = requestAnimationFrame(() => setIsVisible(true))
return () => cancelAnimationFrame(id)
} else {
setIsVisible(false)
}
setIsVisible(false)
}, [isOpen])
useEffect(() => {
updateArrows()
}, [width, updateArrows])
const scrollBy = (delta: number) => {
scrollRef.current?.scrollBy({ left: delta, behavior: 'smooth' })
}
return (
<StyledContainer
ref={ref}
aria-hidden={!isOpen}
isVisible={isVisible}
desktopOffset={!isMobile}
>
{children}
{overflowing && (
<Button
onPress={() => scrollBy(-SCROLL_AMOUNT)}
variant="primaryTextDark"
size="sm"
isDisabled={atStart}
round
>
<RiArrowLeftSLine />
</Button>
)}
<div ref={scrollRef} className={scrollViewport} onScroll={updateArrows}>
{children}
</div>
{overflowing && (
<Button
onPress={() => scrollBy(SCROLL_AMOUNT)}
variant="primaryTextDark"
size="sm"
isDisabled={atEnd}
round
>
<RiArrowRightSLine />
</Button>
)}
</StyledContainer>
)
}