mirror of
https://github.com/suitenumerique/meet.git
synced 2026-09-04 22:55:56 +00:00
✨(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:
committed by
aleb_the_flash
parent
ecf5d443d6
commit
b4ced74b1f
+2
-1
@@ -10,8 +10,9 @@ and this project adheres to
|
|||||||
|
|
||||||
### Added
|
### 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) add synchronizer for room metadata updates
|
||||||
|
- ✨(frontend) make reaction toolbar responsive on small viewports
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
|||||||
+72
-8
@@ -1,15 +1,20 @@
|
|||||||
import { useEffect, useRef, useState } from 'react'
|
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||||
import { styled } from '@/styled-system/jsx'
|
import { styled } from '@/styled-system/jsx'
|
||||||
import { useReactionsToolbar } from '../../hooks/useReactionsToolbar'
|
import { useReactionsToolbar } from '../../hooks/useReactionsToolbar'
|
||||||
import { useIsMobile } from '@/utils/useIsMobile'
|
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', {
|
const StyledContainer = styled('div', {
|
||||||
base: {
|
base: {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
gap: '0.2rem',
|
gap: '0.2rem',
|
||||||
borderRadius: '21px',
|
borderRadius: '21px',
|
||||||
padding: '0.15rem',
|
|
||||||
backgroundColor: 'primaryDark.100',
|
backgroundColor: 'primaryDark.100',
|
||||||
|
maxWidth: '100%',
|
||||||
opacity: 0,
|
opacity: 0,
|
||||||
transform: 'translateY(3.25rem)',
|
transform: 'translateY(3.25rem)',
|
||||||
transition: 'opacity, transform',
|
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 = ({
|
export const ReactionButtonsContainer = ({
|
||||||
children,
|
children,
|
||||||
}: {
|
}: {
|
||||||
@@ -44,28 +66,70 @@ export const ReactionButtonsContainer = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const { isOpen } = useReactionsToolbar()
|
const { isOpen } = useReactionsToolbar()
|
||||||
const isMobile = useIsMobile()
|
const isMobile = useIsMobile()
|
||||||
const ref = useRef<HTMLDivElement>(null)
|
|
||||||
|
const scrollRef = useRef<HTMLDivElement>(null)
|
||||||
|
const { width } = useSize(scrollRef)
|
||||||
|
|
||||||
const [isVisible, setIsVisible] = useState(false)
|
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(() => {
|
useEffect(() => {
|
||||||
if (isOpen) {
|
if (isOpen) {
|
||||||
// defer one frame so the browser paints opacity:0 first
|
|
||||||
const id = requestAnimationFrame(() => setIsVisible(true))
|
const id = requestAnimationFrame(() => setIsVisible(true))
|
||||||
return () => cancelAnimationFrame(id)
|
return () => cancelAnimationFrame(id)
|
||||||
} else {
|
|
||||||
setIsVisible(false)
|
|
||||||
}
|
}
|
||||||
|
setIsVisible(false)
|
||||||
}, [isOpen])
|
}, [isOpen])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
updateArrows()
|
||||||
|
}, [width, updateArrows])
|
||||||
|
|
||||||
|
const scrollBy = (delta: number) => {
|
||||||
|
scrollRef.current?.scrollBy({ left: delta, behavior: 'smooth' })
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledContainer
|
<StyledContainer
|
||||||
ref={ref}
|
|
||||||
aria-hidden={!isOpen}
|
aria-hidden={!isOpen}
|
||||||
isVisible={isVisible}
|
isVisible={isVisible}
|
||||||
desktopOffset={!isMobile}
|
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>
|
</StyledContainer>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user