(frontend) add meeting info side panel with copy functionality

Implement new side panel that provides easy access to meeting information
with copy/paste capabilities. Introduces xs text size to accommodate longer
URLs. Panel includes space for future documentation links about meeting
functionality. Addresses direct user requests for simpler sharing of meeting
details.
This commit is contained in:
lebaudantoine
2025-04-17 22:00:56 +02:00
committed by aleb_the_flash
parent d1a17d2aa9
commit a5fb3b910f
11 changed files with 214 additions and 8 deletions

View File

@@ -397,6 +397,12 @@ const config: Config = {
lineHeight: '1.25rem',
},
},
xs: {
value: {
fontSize: '0.825rem',
lineHeight: '1.15rem',
},
},
badge: {
value: {
fontSize: '0.75rem',

View File

@@ -0,0 +1,75 @@
import { useTranslation } from 'react-i18next'
import { useEffect, useState } from 'react'
import { VStack } from '@/styled-system/jsx'
import { css } from '@/styled-system/css'
import { RiCheckLine, RiFileCopyLine } from '@remixicon/react'
import { Button, Div, Text } from '@/primitives'
import { getRouteUrl } from '@/navigation/getRouteUrl'
import { useRoomData } from '../hooks/useRoomData'
export const Info = () => {
const { t } = useTranslation('rooms', { keyPrefix: 'info' })
const [isCopied, setIsCopied] = useState(false)
useEffect(() => {
if (isCopied) {
const timeout = setTimeout(() => setIsCopied(false), 3000)
return () => clearTimeout(timeout)
}
}, [isCopied])
const data = useRoomData()
const roomUrl = getRouteUrl('room', data?.slug)
return (
<Div
display="flex"
overflowY="scroll"
padding="0 1.5rem"
flexGrow={1}
flexDirection="column"
alignItems="start"
>
<VStack alignItems="start">
<Text
as="h3"
className={css({
display: 'flex',
alignItems: 'center',
})}
>
{t('roomInformation.title')}
</Text>
<Text as="p" variant="xsNote" wrap="pretty">
{roomUrl}
</Text>
<Button
size="sm"
variant={isCopied ? 'success' : 'tertiaryText'}
aria-label={t('roomInformation.button.ariaLabel')}
onPress={() => {
navigator.clipboard.writeText(roomUrl)
setIsCopied(true)
}}
data-attr="copy-info-sidepannel"
style={{
marginLeft: '-8px',
}}
>
{isCopied ? (
<>
<RiCheckLine size={24} style={{ marginRight: '6px' }} />
{t('roomInformation.button.copied')}
</>
) : (
<>
<RiFileCopyLine size={24} style={{ marginRight: '6px' }} />
{t('roomInformation.button.copy')}
</>
)}
</Button>
</VStack>
</Div>
)
}

View File

@@ -12,6 +12,7 @@ import { Chat } from '../prefabs/Chat'
import { Effects } from './effects/Effects'
import { Admin } from './Admin'
import { Tools } from './Tools'
import { Info } from './Info'
type StyledSidePanelProps = {
title: string
@@ -133,6 +134,7 @@ export const SidePanel = () => {
isSidePanelOpen,
isToolsOpen,
isAdminOpen,
isInfoOpen,
isSubPanelOpen,
activeSubPanelId,
} = useSidePanel()
@@ -167,6 +169,9 @@ export const SidePanel = () => {
<Panel isOpen={isAdminOpen}>
<Admin />
</Panel>
<Panel isOpen={isInfoOpen}>
<Info />
</Panel>
</StyledSidePanel>
)
}

View File

@@ -0,0 +1,41 @@
import { useTranslation } from 'react-i18next'
import { RiInformationLine } from '@remixicon/react'
import { css } from '@/styled-system/css'
import { ToggleButton } from '@/primitives'
import { useSidePanel } from '../../hooks/useSidePanel'
import { ToggleButtonProps } from '@/primitives/ToggleButton'
export const InfoToggle = ({
onPress,
...props
}: Partial<ToggleButtonProps>) => {
const { t } = useTranslation('rooms', { keyPrefix: 'controls.info' })
const { isInfoOpen, toggleInfo } = useSidePanel()
const tooltipLabel = isInfoOpen ? 'open' : 'closed'
return (
<div
className={css({
position: 'relative',
display: 'inline-block',
})}
>
<ToggleButton
square
variant="primaryTextDark"
aria-label={t(tooltipLabel)}
tooltip={t(tooltipLabel)}
isSelected={isInfoOpen}
onPress={(e) => {
toggleInfo()
onPress?.(e)
}}
data-attr={`controls-info-${tooltipLabel}`}
{...props}
>
<RiInformationLine />
</ToggleButton>
</div>
)
}

View File

@@ -7,6 +7,7 @@ export enum PanelId {
CHAT = 'chat',
TOOLS = 'tools',
ADMIN = 'admin',
INFO = 'info',
}
export enum SubPanelId {
@@ -24,6 +25,7 @@ export const useSidePanel = () => {
const isChatOpen = activePanelId == PanelId.CHAT
const isToolsOpen = activePanelId == PanelId.TOOLS
const isAdminOpen = activePanelId == PanelId.ADMIN
const isInfoOpen = activePanelId == PanelId.INFO
const isTranscriptOpen = activeSubPanelId == SubPanelId.TRANSCRIPT
const isScreenRecordingOpen = activeSubPanelId == SubPanelId.SCREEN_RECORDING
const isSidePanelOpen = !!activePanelId
@@ -54,6 +56,11 @@ export const useSidePanel = () => {
if (layoutSnap.activeSubPanelId) layoutStore.activeSubPanelId = null
}
const toggleInfo = () => {
layoutStore.activePanelId = isInfoOpen ? null : PanelId.INFO
if (layoutSnap.activeSubPanelId) layoutStore.activeSubPanelId = null
}
const openTranscript = () => {
layoutStore.activeSubPanelId = SubPanelId.TRANSCRIPT
layoutStore.activePanelId = PanelId.TOOLS
@@ -72,6 +79,7 @@ export const useSidePanel = () => {
toggleEffects,
toggleTools,
toggleAdmin,
toggleInfo,
openTranscript,
openScreenRecording,
isSubPanelOpen,
@@ -81,6 +89,7 @@ export const useSidePanel = () => {
isSidePanelOpen,
isToolsOpen,
isAdminOpen,
isInfoOpen,
isTranscriptOpen,
isScreenRecordingOpen,
}

View File

@@ -2,6 +2,7 @@ import { css } from '@/styled-system/css'
import { ChatToggle } from '../../components/controls/ChatToggle'
import { ParticipantsToggle } from '../../components/controls/Participants/ParticipantsToggle'
import { ToolsToggle } from '../../components/controls/ToolsToggle'
import { InfoToggle } from '../../components/controls/InfoToggle'
import { AdminToggle } from '../../components/AdminToggle'
import { useSize } from '../../hooks/useResizeObserver'
import { useState, RefObject } from 'react'
@@ -18,6 +19,7 @@ const NavigationControls = ({
tooltipType = 'instant',
}: Partial<ToggleButtonProps>) => (
<>
<InfoToggle onPress={onPress} tooltipType={tooltipType} />
<ChatToggle onPress={onPress} tooltipType={tooltipType} />
<ParticipantsToggle onPress={onPress} tooltipType={tooltipType} />
<ToolsToggle onPress={onPress} tooltipType={tooltipType} />

View File

@@ -91,6 +91,10 @@
}
}
},
"info": {
"open": "",
"closed": ""
},
"hand": {
"raise": "",
"lower": ""
@@ -171,7 +175,8 @@
"transcript": "",
"screenRecording": "",
"admin": "",
"tools": ""
"tools": "",
"info": ""
},
"content": {
"participants": "",
@@ -180,7 +185,8 @@
"transcript": "",
"screenRecording": "",
"admin": "",
"tools": ""
"tools": "",
"info": ""
},
"closeButton": ""
},
@@ -201,6 +207,16 @@
}
}
},
"info": {
"roomInformation": {
"title": "",
"button": {
"ariaLabel": "",
"copy": "",
"copied": ""
}
}
},
"transcript": {
"start": {
"heading": "",

View File

@@ -90,6 +90,10 @@
}
}
},
"info": {
"open": "Hide information",
"closed": "Show information"
},
"hand": {
"raise": "Raise hand",
"lower": "Lower hand"
@@ -170,7 +174,8 @@
"transcript": "Transcription",
"screenRecording": "Recording",
"admin": "Admin settings",
"tools": "More tools"
"tools": "More tools",
"info": "Meeting information"
},
"content": {
"participants": "participants",
@@ -179,7 +184,8 @@
"transcript": "transcription",
"screenRecording": "recording",
"admin": "admin settings",
"tools": "more tools"
"tools": "more tools",
"info": "meeting information"
},
"closeButton": "Hide {{content}}"
},
@@ -200,6 +206,16 @@
}
}
},
"info": {
"roomInformation": {
"title": "Connection Information",
"button": {
"ariaLabel": "Copy your meeting address",
"copy": "Copy address",
"copied": "Address copied"
}
}
},
"transcript": {
"start": {
"heading": "Transcribe this call",

View File

@@ -90,6 +90,10 @@
}
}
},
"info": {
"open": "Masquer les informations",
"closed": "Afficher les informations"
},
"hand": {
"raise": "Lever la main",
"lower": "Baisser la main"
@@ -170,7 +174,8 @@
"transcript": "Transcription",
"screenRecording": "Enregistrement",
"admin": "Commandes de l'organisateur",
"tools": "Plus d'outils"
"tools": "Plus d'outils",
"info": "Informations sur la réunion"
},
"content": {
"participants": "les participants",
@@ -179,7 +184,8 @@
"transcript": "transcription",
"screenRecording": "enregistrement",
"admin": "commandes de l'organisateur",
"tools": "plus d'outils"
"tools": "plus d'outils",
"info": "informations sur la réunion"
},
"closeButton": "Masquer {{content}}"
},
@@ -200,6 +206,16 @@
}
}
},
"info": {
"roomInformation": {
"title": "Informations de connexions",
"button": {
"ariaLabel": "Copier l'adresse de votre réunion",
"copy": "Copier l'adresse",
"copied": "Adresse copiée"
}
}
},
"transcript": {
"start": {
"heading": "Transcrire cet appel",

View File

@@ -90,6 +90,10 @@
}
}
},
"info": {
"open": "Informatie verbergen",
"closed": "Informatie tonen"
},
"hand": {
"raise": "Hand opsteken",
"lower": "Hand laten zakken"
@@ -170,7 +174,8 @@
"transcript": "Transcriptie",
"screenRecording": "Schermopname",
"admin": "Beheerdersbediening",
"tools": "Meer tools"
"tools": "Meer tools",
"info": "Vergaderinformatie"
},
"content": {
"participants": "deelnemers",
@@ -179,7 +184,8 @@
"screenRecording": "transcriptie",
"transcript": "schermopname",
"admin": "beheerdersbediening",
"tools": "meer tools"
"tools": "meer tools",
"info": "vergaderinformatie"
},
"closeButton": "Verberg {{content}}"
},
@@ -200,6 +206,16 @@
}
}
},
"info": {
"roomInformation": {
"title": "Verbindingsinformatie",
"button": {
"ariaLabel": "Kopieer je vergaderadres",
"copy": "Adres kopiëren",
"copied": "Adres gekopieerd"
}
}
},
"transcript": {
"start": {
"heading": "Transcribeer dit gesprek",

View File

@@ -57,6 +57,10 @@ export const text = cva({
color: 'default.subtle-text',
textStyle: 'sm',
},
xsNote: {
color: 'default.subtle-text',
textStyle: 'xs',
},
inherits: {},
},
centered: {