From b4ced74b1f2443d70740af099c303db828f0d414 Mon Sep 17 00:00:00 2001 From: lebaudantoine Date: Mon, 18 May 2026 16:16:41 +0200 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8(frontend)=20make=20reaction=20toolbar?= =?UTF-8?q?=20responsive=20on=20small=20viewports?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- CHANGELOG.md | 3 +- .../toolbar/ReactionButtonsContainer.tsx | 80 +++++++++++++++++-- 2 files changed, 74 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1fac67c0..205083d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/src/frontend/src/features/reactions/components/toolbar/ReactionButtonsContainer.tsx b/src/frontend/src/features/reactions/components/toolbar/ReactionButtonsContainer.tsx index 9a1897c0..04d6d02f 100644 --- a/src/frontend/src/features/reactions/components/toolbar/ReactionButtonsContainer.tsx +++ b/src/frontend/src/features/reactions/components/toolbar/ReactionButtonsContainer.tsx @@ -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(null) + + const scrollRef = useRef(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 ( - {children} + {overflowing && ( + + )} +
+ {children} +
+ {overflowing && ( + + )}
) }