(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', lineHeight: '1.25rem',
}, },
}, },
xs: {
value: {
fontSize: '0.825rem',
lineHeight: '1.15rem',
},
},
badge: { badge: {
value: { value: {
fontSize: '0.75rem', 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 { Effects } from './effects/Effects'
import { Admin } from './Admin' import { Admin } from './Admin'
import { Tools } from './Tools' import { Tools } from './Tools'
import { Info } from './Info'
type StyledSidePanelProps = { type StyledSidePanelProps = {
title: string title: string
@@ -133,6 +134,7 @@ export const SidePanel = () => {
isSidePanelOpen, isSidePanelOpen,
isToolsOpen, isToolsOpen,
isAdminOpen, isAdminOpen,
isInfoOpen,
isSubPanelOpen, isSubPanelOpen,
activeSubPanelId, activeSubPanelId,
} = useSidePanel() } = useSidePanel()
@@ -167,6 +169,9 @@ export const SidePanel = () => {
<Panel isOpen={isAdminOpen}> <Panel isOpen={isAdminOpen}>
<Admin /> <Admin />
</Panel> </Panel>
<Panel isOpen={isInfoOpen}>
<Info />
</Panel>
</StyledSidePanel> </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', CHAT = 'chat',
TOOLS = 'tools', TOOLS = 'tools',
ADMIN = 'admin', ADMIN = 'admin',
INFO = 'info',
} }
export enum SubPanelId { export enum SubPanelId {
@@ -24,6 +25,7 @@ export const useSidePanel = () => {
const isChatOpen = activePanelId == PanelId.CHAT const isChatOpen = activePanelId == PanelId.CHAT
const isToolsOpen = activePanelId == PanelId.TOOLS const isToolsOpen = activePanelId == PanelId.TOOLS
const isAdminOpen = activePanelId == PanelId.ADMIN const isAdminOpen = activePanelId == PanelId.ADMIN
const isInfoOpen = activePanelId == PanelId.INFO
const isTranscriptOpen = activeSubPanelId == SubPanelId.TRANSCRIPT const isTranscriptOpen = activeSubPanelId == SubPanelId.TRANSCRIPT
const isScreenRecordingOpen = activeSubPanelId == SubPanelId.SCREEN_RECORDING const isScreenRecordingOpen = activeSubPanelId == SubPanelId.SCREEN_RECORDING
const isSidePanelOpen = !!activePanelId const isSidePanelOpen = !!activePanelId
@@ -54,6 +56,11 @@ export const useSidePanel = () => {
if (layoutSnap.activeSubPanelId) layoutStore.activeSubPanelId = null if (layoutSnap.activeSubPanelId) layoutStore.activeSubPanelId = null
} }
const toggleInfo = () => {
layoutStore.activePanelId = isInfoOpen ? null : PanelId.INFO
if (layoutSnap.activeSubPanelId) layoutStore.activeSubPanelId = null
}
const openTranscript = () => { const openTranscript = () => {
layoutStore.activeSubPanelId = SubPanelId.TRANSCRIPT layoutStore.activeSubPanelId = SubPanelId.TRANSCRIPT
layoutStore.activePanelId = PanelId.TOOLS layoutStore.activePanelId = PanelId.TOOLS
@@ -72,6 +79,7 @@ export const useSidePanel = () => {
toggleEffects, toggleEffects,
toggleTools, toggleTools,
toggleAdmin, toggleAdmin,
toggleInfo,
openTranscript, openTranscript,
openScreenRecording, openScreenRecording,
isSubPanelOpen, isSubPanelOpen,
@@ -81,6 +89,7 @@ export const useSidePanel = () => {
isSidePanelOpen, isSidePanelOpen,
isToolsOpen, isToolsOpen,
isAdminOpen, isAdminOpen,
isInfoOpen,
isTranscriptOpen, isTranscriptOpen,
isScreenRecordingOpen, isScreenRecordingOpen,
} }

View File

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

View File

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

View File

@@ -90,6 +90,10 @@
} }
} }
}, },
"info": {
"open": "Hide information",
"closed": "Show information"
},
"hand": { "hand": {
"raise": "Raise hand", "raise": "Raise hand",
"lower": "Lower hand" "lower": "Lower hand"
@@ -170,7 +174,8 @@
"transcript": "Transcription", "transcript": "Transcription",
"screenRecording": "Recording", "screenRecording": "Recording",
"admin": "Admin settings", "admin": "Admin settings",
"tools": "More tools" "tools": "More tools",
"info": "Meeting information"
}, },
"content": { "content": {
"participants": "participants", "participants": "participants",
@@ -179,7 +184,8 @@
"transcript": "transcription", "transcript": "transcription",
"screenRecording": "recording", "screenRecording": "recording",
"admin": "admin settings", "admin": "admin settings",
"tools": "more tools" "tools": "more tools",
"info": "meeting information"
}, },
"closeButton": "Hide {{content}}" "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": { "transcript": {
"start": { "start": {
"heading": "Transcribe this call", "heading": "Transcribe this call",

View File

@@ -90,6 +90,10 @@
} }
} }
}, },
"info": {
"open": "Masquer les informations",
"closed": "Afficher les informations"
},
"hand": { "hand": {
"raise": "Lever la main", "raise": "Lever la main",
"lower": "Baisser la main" "lower": "Baisser la main"
@@ -170,7 +174,8 @@
"transcript": "Transcription", "transcript": "Transcription",
"screenRecording": "Enregistrement", "screenRecording": "Enregistrement",
"admin": "Commandes de l'organisateur", "admin": "Commandes de l'organisateur",
"tools": "Plus d'outils" "tools": "Plus d'outils",
"info": "Informations sur la réunion"
}, },
"content": { "content": {
"participants": "les participants", "participants": "les participants",
@@ -179,7 +184,8 @@
"transcript": "transcription", "transcript": "transcription",
"screenRecording": "enregistrement", "screenRecording": "enregistrement",
"admin": "commandes de l'organisateur", "admin": "commandes de l'organisateur",
"tools": "plus d'outils" "tools": "plus d'outils",
"info": "informations sur la réunion"
}, },
"closeButton": "Masquer {{content}}" "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": { "transcript": {
"start": { "start": {
"heading": "Transcrire cet appel", "heading": "Transcrire cet appel",

View File

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

View File

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