✨(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:
committed by
aleb_the_flash
parent
d1a17d2aa9
commit
a5fb3b910f
@@ -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',
|
||||||
|
|||||||
75
src/frontend/src/features/rooms/livekit/components/Info.tsx
Normal file
75
src/frontend/src/features/rooms/livekit/components/Info.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -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,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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} />
|
||||||
|
|||||||
@@ -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": "",
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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: {
|
||||||
|
|||||||
Reference in New Issue
Block a user