♻️(refactor) Share reactions toggle and paginated toolbar

Unify main and PiP emoji UI via context; remove PiP-only duplicates.
This commit is contained in:
Cyril
2026-05-11 14:02:04 +02:00
parent 8793226427
commit cdc5add2f3
22 changed files with 173 additions and 344 deletions
+1 -2
View File
@@ -14,6 +14,7 @@ and this project adheres to
- ✨(helm) add support multiple transcribe worker / endpoint #1247
- ✨(backend) make LiveKit Egress recording encoding configurable #1288
- ✨(summary) add speaker-to-participant assignment
- ✨(feat) Introduce Picture-in-Picture (PiP) #890
### Changed
@@ -235,8 +236,6 @@ and this project adheres to
- ✨(backend) monitor throttling rate failure through sentry #964
- 🚀(paas) add PaaS deployment scripts, tested on Scalingo #957
- ✨(feat) Introduce Picture-in-Picture (PiP) #890
### Changed
@@ -11,7 +11,7 @@ import { HandToggle } from '@/features/rooms/livekit/components/controls/HandTog
import { StartMediaButton } from '@/features/rooms/livekit/components/controls/StartMediaButton'
import { usePipElementSize } from '../hooks/usePipElementSize'
import { PipOptionsMenu } from './controls/PipOptionsMenu'
import { PipReactionsToggle } from './PipReactionsToggle'
import { ReactionsToggle } from '@/features/reactions/components/ReactionsToggle'
export const CollapsibleControls = {
HAND: 'hand',
@@ -85,8 +85,12 @@ export const PipControlBar = ({
<PipControlsCenter>
<AudioDevicesControl hideMenu />
<VideoDeviceControl hideMenu />
{!hidden.has(CollapsibleControls.REACTIONS) && <PipReactionsToggle />}
{showScreenShare && !hidden.has(CollapsibleControls.SCREEN_SHARE) && <ScreenShareToggle />}
{!hidden.has(CollapsibleControls.REACTIONS) && (
<ReactionsToggle id="pip-reactions-toggle" />
)}
{showScreenShare && !hidden.has(CollapsibleControls.SCREEN_SHARE) && (
<ScreenShareToggle />
)}
{!hidden.has(CollapsibleControls.HAND) && <HandToggle />}
<PipOptionsMenu overflowControls={hidden} />
<LeaveButton />
@@ -1,7 +1,6 @@
import { useTranslation } from 'react-i18next'
import { styled } from '@/styled-system/jsx'
import { useRoomPiP } from '../hooks/useRoomPiP'
import pipIllustration from '/assets/pip.svg'
export const PipPlaceholder = () => {
const { t } = useTranslation('rooms', {
@@ -11,7 +10,13 @@ export const PipPlaceholder = () => {
return (
<Container>
<Illustration src={pipIllustration} alt="" width={102} height={72} aria-hidden="true" />
<Illustration
src="/assets/pip.svg"
alt=""
width={102}
height={72}
aria-hidden="true"
/>
<Title>{t('title')}</Title>
<Description>{t('description')}</Description>
<BringBackLink onClick={close}>{t('bringBack')}</BringBackLink>
@@ -1,34 +0,0 @@
import { useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import { RiEmotionLine } from '@remixicon/react'
import { ToggleButton } from '@/primitives'
import { useSnapshot } from 'valtio'
import { pipLayoutStore } from '../stores/pipLayoutStore'
import { useRegisterKeyboardShortcut } from '@/features/shortcuts/useRegisterKeyboardShortcut'
export const PipReactionsToggle = () => {
const { t } = useTranslation('rooms', { keyPrefix: 'controls.reactions' })
const { showReactionsToolbar: isOpen } = useSnapshot(pipLayoutStore)
const toggle = useCallback(() => {
pipLayoutStore.showReactionsToolbar = !pipLayoutStore.showReactionsToolbar
}, [])
useRegisterKeyboardShortcut({ id: 'reaction', handler: toggle })
return (
<ToggleButton
id="pip-reactions-toggle"
data-attr="pip-reactions-toggle"
square
variant="primaryDark"
aria-label={t('button')}
aria-expanded={isOpen}
tooltip={t('button')}
isSelected={isOpen}
onChange={toggle}
>
<RiEmotionLine />
</ToggleButton>
)
}
@@ -1,69 +0,0 @@
import { useEffect, useRef } from 'react'
import { FocusScope } from '@react-aria/focus'
import { styled } from '@/styled-system/jsx'
import { useSnapshot } from 'valtio'
import { pipLayoutStore } from '../stores/pipLayoutStore'
import { useDelayUnmount } from '@/hooks/useDelayUnmount'
import { usePipElementSize } from '../hooks/usePipElementSize'
import { PipReactionsKeyboardNavigation } from './reactions/PipReactionsKeyboardNavigation'
import { PipReactionsPill } from './reactions/PipReactionsPill'
/**
* Reactions toolbar for the PiP window. Owns only the open/close orchestration;
* layout and pagination live in `PipReactionsPill`, keyboard nav in
* `PipReactionsKeyboardNavigation`.
*/
export const PipReactionsToolbar = () => {
const { showReactionsToolbar: isOpen } = useSnapshot(pipLayoutStore)
// Unmount content after the close transition so hidden emojis leave the tab order.
const renderContent = useDelayUnmount(isOpen, 500)
const contentRef = useRef<HTMLDivElement>(null)
const wrapperRef = useRef<HTMLDivElement>(null)
const { width: availableWidth } = usePipElementSize(wrapperRef)
// Mark the subtree inert during the fade-out so Tab can't land on it.
useEffect(() => {
const el = contentRef.current
if (!el) return
if (isOpen) el.removeAttribute('inert')
else el.setAttribute('inert', '')
}, [isOpen, renderContent])
return (
<Wrapper ref={wrapperRef} isOpen={isOpen}>
{renderContent && (
<div ref={contentRef}>
{/* eslint-disable-next-line jsx-a11y/no-autofocus */}
<FocusScope autoFocus>
<PipReactionsKeyboardNavigation>
<PipReactionsPill
isOpen={isOpen}
availableWidth={availableWidth}
/>
</PipReactionsKeyboardNavigation>
</FocusScope>
</div>
)}
</Wrapper>
)
}
const Wrapper = styled('div', {
base: {
display: 'flex',
justifyContent: 'center',
overflow: 'hidden',
maxHeight: 0,
padding: '0 0.5rem',
transition:
'max-height 0.5s cubic-bezier(0.4, 0, 0.2, 1), padding 0.5s cubic-bezier(0.4, 0, 0.2, 1)',
},
variants: {
isOpen: {
true: {
maxHeight: '60px',
padding: '0.5rem 0.5rem 0.25rem',
},
},
},
})
@@ -7,13 +7,14 @@ import {
SidePanelStoreProvider,
useSidePanel,
} from '@/features/rooms/livekit/hooks/useSidePanel'
import { ReactionsToolbarStoreProvider } from '@/features/reactions/hooks/useReactionsToolbar'
import { pipLayoutStore } from '../stores/pipLayoutStore'
import { useEscapeDismiss } from '../hooks/useEscapeDismiss'
import { usePipKeyboardShortcuts } from '../hooks/usePipKeyboardShortcuts'
import { usePipRestoreFocus } from '../hooks/usePipRestoreFocus'
import { usePipFocusModality } from '../hooks/usePipFocusModality'
import { PipControlBar } from './PipControlBar'
import { PipReactionsToolbar } from './PipReactionsToolbar'
import { ReactionsToolbar } from '@/features/reactions/components/toolbar/ReactionsToolbar'
import { PipStage } from './layouts/PipStage'
import { PipNotificationOverlay } from './notifications/PipNotificationOverlay'
import { PipConnectionStateToast } from './notifications/PipConnectionStateToast'
@@ -22,7 +23,9 @@ import { PipReactionPortals } from './PipReactionPortals'
export const PipView = () => {
return (
<SidePanelStoreProvider value={pipLayoutStore}>
<PipViewContent />
<ReactionsToolbarStoreProvider value={pipLayoutStore}>
<PipViewContent />
</ReactionsToolbarStoreProvider>
</SidePanelStoreProvider>
)
}
@@ -62,7 +65,10 @@ const PipViewContent = () => {
aria-label={t('windowLabel')}
>
<PipStage />
<PipReactionsToolbar />
<ReactionsToolbar
toggleId="pip-reactions-toggle"
controlBarId="pip-control-bar"
/>
<PipControlBar showScreenShare={browserSupportsScreenSharing} />
<SidePanel />
<PipReactionPortals />
@@ -14,10 +14,9 @@ type PipOptionsMenuItemsProps = {
export const PipOptionsMenuItems = ({
overflowControls,
}: PipOptionsMenuItemsProps) => {
const hasOverflow = (overflowControls?.size ?? 0) > 0
const hasOverflowControls = hasOverflow && overflowControls
const hasOverflowControls = hasOverflow && overflowControls
return (
<RACMenu
@@ -1,17 +1,12 @@
import React from 'react'
import { MenuItem } from 'react-aria-components'
import {
RiHand,
RiArrowUpLine,
RiEmotionLine,
} from '@remixicon/react'
import { RiHand, RiArrowUpLine, RiEmotionLine } from '@remixicon/react'
import { useTranslation } from 'react-i18next'
import { Track } from 'livekit-client'
import { pipLayoutStore } from '@/features/pip/stores/pipLayoutStore'
import { menuRecipe } from '@/primitives/menuRecipe'
import { useRoomContext, useTrackToggle } from '@livekit/components-react'
import { useRaisedHand } from '@/features/rooms/livekit/hooks/useRaisedHand'
import { useSnapshot } from 'valtio'
import { useReactionsToolbar } from '@/features/reactions/hooks/useReactionsToolbar'
import { CollapsibleControls, type CollapsibleControl } from '../PipControlBar'
type PipOverflowItemsProps = {
@@ -31,10 +26,7 @@ export const PipOverflowItems = ({
source: Track.Source.ScreenShare,
captureOptions: { audio: true, selfBrowserSurface: 'include' },
})
const pipLayoutSnap = useSnapshot(pipLayoutStore)
const toggleReactions = () => {
pipLayoutStore.showReactionsToolbar = !pipLayoutSnap.showReactionsToolbar
}
const { toggle: toggleReactions } = useReactionsToolbar()
const itemClass = menuRecipe({ icon: true, variant: 'dark' }).item
return (
@@ -47,11 +39,19 @@ export const PipOverflowItems = ({
)}
{overflowControls.has(CollapsibleControls.SCREEN_SHARE) && (
<MenuItem
onAction={() => screenShareProps.onClick?.({} as React.MouseEvent<HTMLButtonElement>)}
onAction={() =>
screenShareProps.onClick?.(
{} as React.MouseEvent<HTMLButtonElement>
)
}
className={itemClass}
>
<RiArrowUpLine size={20} />
{t(isScreenSharing ? 'controls.screenShare.stop' : 'controls.screenShare.start')}
{t(
isScreenSharing
? 'controls.screenShare.stop'
: 'controls.screenShare.start'
)}
</MenuItem>
)}
{overflowControls.has(CollapsibleControls.HAND) && (
@@ -22,10 +22,7 @@ export const PipFocusLayout = memo(
return (
<FocusContainer>
<MainSlot>
<ParticipantTile
key={getTrackKey(mainTrack)}
trackRef={mainTrack}
/>
<ParticipantTile key={getTrackKey(mainTrack)} trackRef={mainTrack} />
</MainSlot>
{thumbnailTrack && (
<Thumbnail>
@@ -36,7 +36,13 @@ export const PipNotificationOverlay = () => {
<StyledToastContainer
key={toast.key}
aria-atomic="true"
css={{ margin: 0, marginLeft: 0, display: 'flex', alignItems: 'center', paddingRight: '0.25rem' }}
css={{
margin: 0,
marginLeft: 0,
display: 'flex',
alignItems: 'center',
paddingRight: '0.25rem',
}}
>
<PipToastBody toast={toast} />
<Button
@@ -63,4 +69,3 @@ const Region = styled('div', {
width: '100%',
},
})
@@ -1,4 +1,4 @@
import { useCallback } from 'react'
import { useCallback, useRef } from 'react'
import { useTranslation } from 'react-i18next'
import { RiEmotionLine } from '@remixicon/react'
import { ToggleButton } from '@/primitives'
@@ -6,40 +6,37 @@ import { ToggleButton } from '@/primitives'
import { useRegisterKeyboardShortcut } from '@/features/shortcuts/useRegisterKeyboardShortcut'
import { REACTIONS_TOOLBAR_ID } from '../constants'
import { useReactionsToolbar } from '../hooks/useReactionsToolbar'
import { layoutStore } from '@/stores/layout'
import { useRoomPiP } from '@/features/pip/hooks/useRoomPiP'
const focusReactionsToolbar = () => {
document
.getElementById(REACTIONS_TOOLBAR_ID)
?.querySelector<HTMLElement>('button')
?.focus()
type ReactionsToggleProps = {
id?: string
}
export const ReactionsToggle = () => {
export const ReactionsToggle = ({
id = 'reactions-toggle',
}: ReactionsToggleProps) => {
const { t } = useTranslation('rooms', { keyPrefix: 'controls.reactions' })
const { isOpen: isPiPOpen } = useRoomPiP()
const { isOpen, toggle } = useReactionsToolbar()
const buttonRef = useRef<HTMLButtonElement>(null)
const handleShortcut = useCallback(() => {
if (layoutStore.showReactionsToolbar) {
focusReactionsToolbar()
if (isOpen) {
const doc = buttonRef.current?.ownerDocument ?? document
doc
.getElementById(REACTIONS_TOOLBAR_ID)
?.querySelector<HTMLElement>('button')
?.focus()
} else {
layoutStore.showReactionsToolbar = true
toggle()
}
}, [])
}, [isOpen, toggle])
useRegisterKeyboardShortcut({
id: 'reaction',
handler: handleShortcut,
isDisabled: isPiPOpen,
})
useRegisterKeyboardShortcut({ id: 'reaction', handler: handleShortcut })
return (
<ToggleButton
id="reactions-toggle"
data-attr="reactions-toggle"
ref={buttonRef}
id={id}
data-attr={id}
square
variant="primaryDark"
aria-label={t('button')}
@@ -2,31 +2,24 @@ import { useRef, type ReactNode } from 'react'
import { useTranslation } from 'react-i18next'
import { useFocusManager } from '@react-aria/focus'
import { findFirstFocusable } from '@/utils/dom'
import { pipLayoutStore } from '@/features/pip/stores/pipLayoutStore'
import { useEscapeDismiss } from '@/features/pip/hooks/useEscapeDismiss'
import { useReactionsToolbar } from '../../hooks/useReactionsToolbar'
import { REACTIONS_TOOLBAR_ID } from '../../constants'
const REACTIONS_TOGGLE_ID = 'pip-reactions-toggle'
const CONTROL_BAR_ID = 'pip-control-bar'
const closeToolbar = () => {
pipLayoutStore.showReactionsToolbar = false
type Props = {
children: ReactNode
toggleId?: string
controlBarId?: string
}
/** Keyboard navigation for the PiP reactions toolbar (mirrors the main app). */
export const PipReactionsKeyboardNavigation = ({
export const ReactionsKeyboardNavigation = ({
children,
}: {
children: ReactNode
}) => {
toggleId = 'reactions-toggle',
controlBarId = 'control-bar',
}: Props) => {
const { t } = useTranslation('rooms', { keyPrefix: 'controls.reactions' })
const focusManager = useFocusManager()
const rootRef = useRef<HTMLDivElement>(null)
useEscapeDismiss(rootRef, true, () => {
const doc = rootRef.current?.ownerDocument ?? document
doc.getElementById(REACTIONS_TOGGLE_ID)?.focus()
closeToolbar()
})
const { close } = useReactionsToolbar()
const onFocus = (event: React.FocusEvent<HTMLDivElement>) => {
const fromOutside = !event.currentTarget.contains(event.relatedTarget)
@@ -34,6 +27,8 @@ export const PipReactionsKeyboardNavigation = ({
}
const onKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {
const doc = rootRef.current?.ownerDocument ?? document
switch (event.key) {
case 'ArrowRight':
focusManager?.focusNext({ wrap: true })
@@ -41,11 +36,15 @@ export const PipReactionsKeyboardNavigation = ({
case 'ArrowLeft':
focusManager?.focusPrevious({ wrap: true })
break
case 'Escape':
event.preventDefault()
doc.getElementById(toggleId)?.focus()
close()
break
case 'Tab':
if (!event.shiftKey) {
event.preventDefault()
const doc = rootRef.current?.ownerDocument ?? document
findFirstFocusable(doc.getElementById(CONTROL_BAR_ID))?.focus()
findFirstFocusable(doc.getElementById(controlBarId))?.focus()
}
break
}
@@ -54,6 +53,7 @@ export const PipReactionsKeyboardNavigation = ({
return (
<div
ref={rootRef}
id={REACTIONS_TOOLBAR_ID}
role="toolbar"
aria-label={t('toolbar')}
onFocus={onFocus}
@@ -2,21 +2,17 @@ import { useCallback, useEffect, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { RiArrowLeftSLine, RiArrowRightSLine } from '@remixicon/react'
import { styled } from '@/styled-system/jsx'
import { ReactionButton } from '@/features/reactions/components/toolbar/ReactionButton'
import { ReactionButton } from './ReactionButton'
import {
computeReactionsPage,
getMaxPageStart,
} from '../../utils/pipReactionsPagination'
} from '../../utils/reactionsPagination'
type Props = { isOpen: boolean; availableWidth: number }
/**
* Paginated emoji pill with animated entry/exit. Responsibility: layout the
* visible emojis for the currently available width and expose prev/next arrows.
*/
export const PipReactionsPill = ({ isOpen, availableWidth }: Props) => {
export const ReactionsPill = ({ isOpen, availableWidth }: Props) => {
const { t } = useTranslation('rooms', {
keyPrefix: 'options.items.pictureInPicture',
keyPrefix: 'controls.reactions',
})
const [isVisible, setIsVisible] = useState(false)
const [pageStart, setPageStart] = useState(0)
@@ -36,7 +32,6 @@ export const PipReactionsPill = ({ isOpen, availableWidth }: Props) => {
[availableWidth, pageStart]
)
// Clamp pageStart if the window was resized and the current page no longer fits.
useEffect(() => {
if (!hasOverflow) {
setPageStart(0)
@@ -1,155 +1,69 @@
import { FocusScope, useFocusManager } from '@react-aria/focus'
import { REACTIONS_TOOLBAR_ID } from '../../constants'
import { useReactionsToolbar } from '../../hooks/useReactionsToolbar'
import { ReactionButton } from './ReactionButton'
import { Emoji } from '../../types'
import { useEffect, useRef } from 'react'
import { FocusScope } from '@react-aria/focus'
import { styled } from '@/styled-system/jsx'
import { layoutStore } from '@/stores/layout'
import { getFirstControlBarFocusable } from '@/utils/dom'
import { useIsMobile } from '@/utils/useIsMobile'
import { useEffect, useRef, useState } from 'react'
import { useReactionsToolbar } from '../../hooks/useReactionsToolbar'
import { useDelayUnmount } from '@/hooks/useDelayUnmount'
import { useTranslation } from 'react-i18next'
import { usePipElementSize } from '@/features/pip/hooks/usePipElementSize'
import { ReactionsKeyboardNavigation } from './ReactionsKeyboardNavigation'
import { ReactionsPill } from './ReactionsPill'
const Container = styled('div', {
type ReactionsToolbarProps = {
toggleId?: string
controlBarId?: string
}
export const ReactionsToolbar = ({
toggleId = 'reactions-toggle',
controlBarId = 'control-bar',
}: ReactionsToolbarProps) => {
const { isOpen } = useReactionsToolbar()
const renderContent = useDelayUnmount(isOpen, 500)
const contentRef = useRef<HTMLDivElement>(null)
const wrapperRef = useRef<HTMLDivElement>(null)
const { width: availableWidth } = usePipElementSize(wrapperRef)
useEffect(() => {
const el = contentRef.current
if (!el) return
if (isOpen) el.removeAttribute('inert')
else el.setAttribute('inert', '')
}, [isOpen, renderContent])
return (
<Wrapper ref={wrapperRef} isOpen={isOpen}>
{renderContent && (
<div ref={contentRef}>
{/* eslint-disable-next-line jsx-a11y/no-autofocus */}
<FocusScope autoFocus>
<ReactionsKeyboardNavigation
toggleId={toggleId}
controlBarId={controlBarId}
>
<ReactionsPill isOpen={isOpen} availableWidth={availableWidth} />
</ReactionsKeyboardNavigation>
</FocusScope>
</div>
)}
</Wrapper>
)
}
const Wrapper = styled('div', {
base: {
display: 'flex',
justifyContent: 'center',
position: 'absolute',
bottom: 'var(--sizes-room-control-bar)',
left: 0,
right: 0,
pointerEvents: 'none',
},
})
const StyledStrip = styled('div', {
base: {
display: 'flex',
gap: '0.2rem',
borderRadius: '21px',
padding: '0.15rem',
backgroundColor: 'primaryDark.100',
opacity: 0,
transform: 'translateY(3.25rem)',
transition: 'opacity, transform',
transitionDuration: '0.5s',
transitionTimingFunction: 'cubic-bezier(0.4, 0, 0.2, 1)',
pointerEvents: 'none',
overflow: 'hidden',
maxHeight: 0,
width: '100%',
transition:
'max-height 0.5s cubic-bezier(0.4, 0, 0.2, 1), padding 0.5s cubic-bezier(0.4, 0, 0.2, 1)',
},
variants: {
isVisible: {
isOpen: {
true: {
opacity: 1,
transform: 'translateY(0)',
pointerEvents: 'auto',
},
},
desktopOffset: {
true: {
// Ideally this value should be calculated dynamically in JavaScript to keep
// the reaction toolbar perfectly centered relative to the reaction toggle.
// However, for simplicity and to follow a pragmatic 80/20 approach,
// this value is currently hardcoded in CSS.
marginRight: '30px',
maxHeight: '60px',
padding: '0.5rem 0',
},
},
},
})
const Strip = ({ children }: { children: React.ReactNode }) => {
const { isOpen } = useReactionsToolbar()
const isMobile = useIsMobile()
const ref = useRef<HTMLDivElement>(null)
const [isVisible, setIsVisible] = useState(false)
useEffect(() => {
if (isOpen) {
// defer one frame so the browser paints opacity:0 first
const id = requestAnimationFrame(() => setIsVisible(true))
return () => cancelAnimationFrame(id)
} else {
setIsVisible(false)
}
}, [isOpen])
return (
<StyledStrip
ref={ref}
aria-hidden={!isOpen}
isVisible={isVisible}
desktopOffset={!isMobile}
>
{children}
</StyledStrip>
)
}
const KeyboardNavigation = ({ children }: { children: React.ReactNode }) => {
const { t } = useTranslation('rooms', { keyPrefix: 'controls.reactions' })
const focusManager = useFocusManager()
const onFocus = (e: React.FocusEvent<HTMLDivElement>) => {
const comingFromOutside = !e.currentTarget.contains(e.relatedTarget)
if (comingFromOutside) {
focusManager?.focusFirst()
}
}
const onKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {
switch (e.key) {
case 'ArrowRight':
focusManager?.focusNext({ wrap: true })
break
case 'ArrowLeft':
focusManager?.focusPrevious({ wrap: true })
break
case 'Escape':
e.preventDefault()
document.getElementById('reactions-toggle')?.focus()
layoutStore.showReactionsToolbar = false
break
case 'Tab':
if (!e.shiftKey) {
e.preventDefault()
getFirstControlBarFocusable('control-bar')?.focus()
}
break
}
}
return (
<div
id={REACTIONS_TOOLBAR_ID}
role="toolbar"
aria-label={t('toolbar')}
onKeyDown={onKeyDown}
onFocus={onFocus}
>
{children}
</div>
)
}
export const ReactionsToolbar = () => {
const { isOpen } = useReactionsToolbar()
const shouldMount = useDelayUnmount(isOpen, 300)
if (!shouldMount) return null
return (
<Container>
{/* eslint-disable-next-line jsx-a11y/no-autofocus*/}
<FocusScope autoFocus>
<KeyboardNavigation>
<Strip>
{Object.values(Emoji).map((emoji) => (
<ReactionButton key={emoji} emoji={emoji} />
))}
</Strip>
</KeyboardNavigation>
</FocusScope>
</Container>
)
}
@@ -1,13 +1,28 @@
import { createContext, useContext } from 'react'
import { useSnapshot } from 'valtio'
import { layoutStore } from '@/stores/layout'
export type ReactionsToolbarStore = {
showReactionsToolbar: boolean
}
const ReactionsToolbarStoreContext =
createContext<ReactionsToolbarStore>(layoutStore)
export const ReactionsToolbarStoreProvider =
ReactionsToolbarStoreContext.Provider
export const useReactionsToolbar = () => {
const layoutSnap = useSnapshot(layoutStore)
const store = useContext(ReactionsToolbarStoreContext)
const snap = useSnapshot(store)
return {
isOpen: layoutSnap.showReactionsToolbar,
isOpen: snap.showReactionsToolbar,
toggle: () => {
layoutStore.showReactionsToolbar = !layoutSnap.showReactionsToolbar
store.showReactionsToolbar = !store.showReactionsToolbar
},
close: () => {
store.showReactionsToolbar = false
},
}
}
+2 -2
View File
@@ -219,6 +219,8 @@
"toolbar": "Reaktion senden",
"announce": "{{name}} : {{emoji}}",
"you": "du",
"previousReactions": "Vorherige Reaktionen",
"nextReactions": "Nächste Reaktionen",
"emojis": {
"thumbs-up": "Daumen hoch",
"thumbs-down": "Daumen runter",
@@ -249,8 +251,6 @@
"windowLabel": "Bild-im-Bild Besprechung",
"stage": "Teilnehmer",
"controlBar": "Besprechungssteuerung",
"previousReactions": "Vorherige Reaktionen",
"nextReactions": "Nächste Reaktionen",
"notificationsLabel": "Benachrichtigungen",
"dismissNotification": "Benachrichtigung schließen",
"connection": {
+2 -2
View File
@@ -219,6 +219,8 @@
"toolbar": "Send reaction",
"announce": "{{name}} : {{emoji}}",
"you": "you",
"previousReactions": "Previous reactions",
"nextReactions": "Next reactions",
"emojis": {
"thumbs-up": "thumbs up",
"thumbs-down": "thumbs down",
@@ -249,8 +251,6 @@
"windowLabel": "Picture-in-picture meeting",
"stage": "Participants",
"controlBar": "Meeting controls",
"previousReactions": "Previous reactions",
"nextReactions": "Next reactions",
"notificationsLabel": "Notifications",
"dismissNotification": "Dismiss notification",
"connection": {
+2 -2
View File
@@ -219,6 +219,8 @@
"toolbar": "Envoyer une réaction",
"announce": "{{name}} : {{emoji}}",
"you": "vous",
"previousReactions": "Réactions précédentes",
"nextReactions": "Réactions suivantes",
"emojis": {
"thumbs-up": "pouce levé",
"thumbs-down": "pouce baissé",
@@ -249,8 +251,6 @@
"windowLabel": "Réunion en image dans l'image",
"stage": "Participants",
"controlBar": "Commandes de la réunion",
"previousReactions": "Réactions précédentes",
"nextReactions": "Réactions suivantes",
"notificationsLabel": "Notifications",
"dismissNotification": "Fermer la notification",
"connection": {
+2 -2
View File
@@ -219,6 +219,8 @@
"toolbar": "Stuur reactie",
"announce": "{{name}} : {{emoji}}",
"you": "U",
"previousReactions": "Vorige reacties",
"nextReactions": "Volgende reacties",
"emojis": {
"thumbs-up": "duim omhoog",
"thumbs-down": "duim omlaag",
@@ -249,8 +251,6 @@
"windowLabel": "Beeld-in-beeld vergadering",
"stage": "Deelnemers",
"controlBar": "Vergaderbesturing",
"previousReactions": "Vorige reacties",
"nextReactions": "Volgende reacties",
"notificationsLabel": "Meldingen",
"dismissNotification": "Melding sluiten",
"connection": {
+1 -4
View File
@@ -24,10 +24,7 @@ export const Menu = ({
return (
<MenuTrigger>
{trigger}
<StyledPopover
placement={placement}
boundaryElement={boundaryElement}
>
<StyledPopover placement={placement} boundaryElement={boundaryElement}>
<Box size="sm" type="popover" variant={variant}>
{menu}
</Box>
@@ -7,5 +7,4 @@ import { createContext, useContext } from 'react'
*/
export const VisualOnlyTooltipsContext = createContext(false)
export const useVisualOnlyTooltips = () =>
useContext(VisualOnlyTooltipsContext)
export const useVisualOnlyTooltips = () => useContext(VisualOnlyTooltipsContext)