diff --git a/src/assets/icons/ChatIcon.tsx b/src/assets/icons/ChatIcon.tsx deleted file mode 100644 index e3e7c49..0000000 --- a/src/assets/icons/ChatIcon.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import * as React from 'react' -import type { SVGProps } from 'react' -const SvgChatIcon = (props: SVGProps) => ( - - - - -) -export default SvgChatIcon diff --git a/src/assets/icons/PeopleIcon.tsx b/src/assets/icons/PeopleIcon.tsx deleted file mode 100644 index 5a89b8c..0000000 --- a/src/assets/icons/PeopleIcon.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import * as React from 'react' -import type { SVGProps } from 'react' - -const PeopleIcon = (props: SVGProps) => ( - - - -) - -export default PeopleIcon diff --git a/src/components/Icons/CameraIcon.tsx b/src/components/Icons/CameraIcon.tsx new file mode 100644 index 0000000..677f99b --- /dev/null +++ b/src/components/Icons/CameraIcon.tsx @@ -0,0 +1,32 @@ +import React from 'react' +import { Props } from './Icons.type' + +const CameraIcon = ({ enabled = true, ...props }: Props) => { + return enabled ? ( + + + + + + ) : ( + + + + + ) +} + +export default CameraIcon diff --git a/src/components/Icons/ChatIcon.tsx b/src/components/Icons/ChatIcon.tsx new file mode 100644 index 0000000..7e049f4 --- /dev/null +++ b/src/components/Icons/ChatIcon.tsx @@ -0,0 +1,14 @@ +import React from 'react' +import { Props } from './Icons.type' + +const ChatIcon = (props: Props) => ( + + + +) +export default ChatIcon diff --git a/src/components/Icons/Icons.type.ts b/src/components/Icons/Icons.type.ts new file mode 100644 index 0000000..f7060a6 --- /dev/null +++ b/src/components/Icons/Icons.type.ts @@ -0,0 +1,5 @@ +import { SVGProps } from 'react' + +export type Props = SVGProps & { + enabled?: boolean +} diff --git a/src/assets/icons/LeaveIcon.tsx b/src/components/Icons/LeaveIcon.tsx similarity index 82% rename from src/assets/icons/LeaveIcon.tsx rename to src/components/Icons/LeaveIcon.tsx index c16346d..c9a5b50 100644 --- a/src/assets/icons/LeaveIcon.tsx +++ b/src/components/Icons/LeaveIcon.tsx @@ -1,6 +1,7 @@ -import * as React from 'react' -import type { SVGProps } from 'react' -const SvgLeaveIcon = (props: SVGProps) => ( +import React from 'react' +import { Props } from './Icons.type' + +const LeaveIcon = (props: Props) => ( ) => ( /> ) -export default SvgLeaveIcon + +export default LeaveIcon diff --git a/src/components/Icons/MicrophoneIcon.tsx b/src/components/Icons/MicrophoneIcon.tsx new file mode 100644 index 0000000..6ca2554 --- /dev/null +++ b/src/components/Icons/MicrophoneIcon.tsx @@ -0,0 +1,53 @@ +import React from 'react' +import { Props } from './Icons.type' + +const MicrophoneIcon = ({ enabled = true, ...props }: Props) => { + return enabled ? ( + + + + + + ) : ( + + + + + + + ) +} + +export default MicrophoneIcon diff --git a/src/components/Icons/PeopleIcon.tsx b/src/components/Icons/PeopleIcon.tsx new file mode 100644 index 0000000..0215c0f --- /dev/null +++ b/src/components/Icons/PeopleIcon.tsx @@ -0,0 +1,26 @@ +import React from 'react' +import { Props } from './Icons.type' + +const PeopleIcon = (props: Props) => ( + + + + + + + + + +) + +export default PeopleIcon diff --git a/src/components/Icons/PhoneIcon.tsx b/src/components/Icons/PhoneIcon.tsx new file mode 100644 index 0000000..29703c9 --- /dev/null +++ b/src/components/Icons/PhoneIcon.tsx @@ -0,0 +1,30 @@ +import React from 'react' +import { Props } from './Icons.type' + +const PhoneIcon = (props: Props) => ( + + + + + +) + +export default PhoneIcon diff --git a/src/assets/icons/SendIcon.tsx b/src/components/Icons/SendIcon.tsx similarity index 81% rename from src/assets/icons/SendIcon.tsx rename to src/components/Icons/SendIcon.tsx index 5e3c9d4..c5b653e 100644 --- a/src/assets/icons/SendIcon.tsx +++ b/src/components/Icons/SendIcon.tsx @@ -1,7 +1,7 @@ -import * as React from 'react' -import type { SVGProps } from 'react' +import React from 'react' +import { Props } from './Icons.type' -const SendIcon = (props: SVGProps) => ( +const SendIcon = (props: Props) => ( { + return enabled ? ( + + + + + ) : ( + + + + + ) +} + +export default ShareScreenIcon diff --git a/src/components/Icons/index.ts b/src/components/Icons/index.ts new file mode 100644 index 0000000..e8a637a --- /dev/null +++ b/src/components/Icons/index.ts @@ -0,0 +1,8 @@ +export { default as CameraIcon } from './CameraIcon' +export { default as ChatIcon } from './ChatIcon' +export { default as LeaveIcon } from './LeaveIcon' +export { default as MicrophoneIcon } from './MicrophoneIcon' +export { default as PeopleIcon } from './PeopleIcon' +export { default as PhoneIcon } from './PhoneIcon' +export { default as SendIcon } from './SendIcon' +export { default as ShareScreenIcon } from './ShareScreenIcon' diff --git a/src/components/VideoConference/ControlBar/ControlBar.module.css b/src/components/VideoConference/ControlBar/ControlBar.module.css index 998a72a..be0e292 100644 --- a/src/components/VideoConference/ControlBar/ControlBar.module.css +++ b/src/components/VideoConference/ControlBar/ControlBar.module.css @@ -1,7 +1,19 @@ .ControlBarContainer { + display: flex; position: relative; + align-items: center; height: var(--lk-control-bar-height); - border-top: none; + margin-top: var(--lk-control-bar-margin-top); + margin-bottom: var(--lk-control-bar-margin-bottom); + justify-content: space-between; +} + +.ControlBarCenterButtonGroup { + display: flex; + gap: 0.5rem; + align-items: center; + justify-content: center; + flex: 1; } .ControlBarRightButtonGroup { @@ -13,6 +25,110 @@ justify-content: center; } +.controlBarButtonGroup { + display: flex; + align-items: center; +} + +.controlBarButton:global(.lk-button) { + display: flex; + justify-content: center; + align-items: center; + width: 48px; + height: 48px; + background-color: #716b7c; + border: 0; + border-radius: 100%; + padding: 0; +} + +.controlBarButton:global(.lk-button):hover { + background-color: #928ea4; +} + +.controlBarMenuButton:global(.lk-button-group-menu .lk-button.lk-button-menu) { + background-color: transparent; +} + +.controlBarMenuButton:global(.lk-button-group-menu .lk-button[aria-pressed='true'])::after { + transform: rotate(135deg); +} + +.controlBarMenuButton:global(.lk-button-group-menu .lk-device-menu) { + left: 10px !important; +} + +.disabled:global(.lk-button) { + background-color: #ff2d55; +} + +.disabled:global(.lk-button):hover { + background-color: #ff2d55; +} + +.screenShareEnabled:global(.lk-button) { + background-color: #ffffff; +} + +.screenShareEnabled:global(.lk-button):hover { + background-color: #ffffff; +} + +.stopSharingButton:global(.ui.button) { + display: flex; + align-items: center; + padding: 0 10px; + height: 30px; + gap: 8px; + width: 134px; + background-color: #ffffff; + font-size: 11px; + font-weight: 700; +} + +.stopSharingButton:global(.ui.button):hover { + transform: none; +} + +.stopSharingCloseButton { + background-image: url('../../../assets/icons/Close.svg'); + background-size: contain; + background-position: center; + filter: invert(50%); + width: 16px; + height: 16px; + border: 1px solid #a09ba8; +} + +.microphoneButtonGroup { + display: flex; + align-items: center; + gap: 8px; +} + .chatToggleButton { background-color: transparent; } + +.chatToggleIcon { + height: 24px; + width: 24px; +} + +.disconnectButton { + margin-left: 61px; + display: flex; + height: 45px; + padding: 14px 44px; + justify-content: center; + align-items: center; + gap: 10px; + background-color: #ff2d55; + border-radius: 6px; + color: #fff; + text-align: center; + font-size: 14px; + font-weight: 600; + line-height: normal; + text-transform: uppercase; +} diff --git a/src/components/VideoConference/ControlBar/ControlBar.tsx b/src/components/VideoConference/ControlBar/ControlBar.tsx index 77aedf6..109096d 100644 --- a/src/components/VideoConference/ControlBar/ControlBar.tsx +++ b/src/components/VideoConference/ControlBar/ControlBar.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useMemo, useRef, useState } from 'react' +import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { supportsScreenSharing } from '@livekit/components-core' import { ChatToggle, @@ -7,15 +7,18 @@ import { StartAudio, TrackToggle, useLocalParticipantPermissions, - useRoomContext + useRoomContext, + useTrackToggle } from '@livekit/components-react' +import classNames from 'classnames' import { LocalAudioTrack, LocalVideoTrack, Track } from 'livekit-client' -import ChatIcon from '../../../assets/icons/ChatIcon' -import LeaveIcon from '../../../assets/icons/LeaveIcon' +import { t } from 'decentraland-dapps/dist/modules/translation/utils' +import { Button, Popup } from 'decentraland-ui' import { useLayoutContext } from '../../../hooks/useLayoutContext' import { useMediaQuery } from '../../../hooks/useMediaQuery' import { usePreviewTracks } from '../../../hooks/usePreviewTracks' import { mergeProps } from '../../../utils/mergeProps' +import { CameraIcon, ChatIcon, MicrophoneIcon, PhoneIcon, ShareScreenIcon } from '../../Icons' import PeoplePanelToggleButton from './PeoplePanelToggleButton' import { ControlBarProps, DEFAULT_USER_CHOICES } from './ControlBar.types' import styles from './ControlBar.module.css' @@ -38,6 +41,7 @@ import styles from './ControlBar.module.css' */ export function ControlBar({ variation, controls, ...props }: ControlBarProps) { const [isChatOpen, setIsChatOpen] = useState(false) + const [confirmStopSharing, setConfirmStopSharing] = useState(false) const layoutContext = useLayoutContext() const { options: { videoCaptureDefaults, audioCaptureDefaults } @@ -56,6 +60,7 @@ export function ControlBar({ variation, controls, ...props }: ControlBarProps) { visibleControls.chat = false visibleControls.microphone = false visibleControls.screenShare = false + visibleControls.peoplePanel = false } else { visibleControls.camera ??= localPermissions.canPublish visibleControls.microphone ??= localPermissions.canPublish @@ -68,13 +73,12 @@ export function ControlBar({ variation, controls, ...props }: ControlBarProps) { const browserSupportsScreenSharing = supportsScreenSharing() - const [isScreenShareEnabled, setIsScreenShareEnabled] = useState(false) + const handleStopScreenShare = useCallback(() => { + toggleScreenShare() + setConfirmStopSharing(false) + }, []) - const onScreenShareChange = (enabled: boolean) => { - setIsScreenShareEnabled(enabled) - } - - const htmlProps = mergeProps({ className: `lk-control-bar ${styles.ControlBarContainer}` }, props) + const htmlProps = mergeProps({ className: styles.ControlBarContainer }, props) const [videoEnabled, setVideoEnabled] = useState(visibleControls.camera ?? DEFAULT_USER_CHOICES.videoEnabled) const initialVideoDeviceId = (videoCaptureDefaults?.deviceId as string) ?? DEFAULT_USER_CHOICES.videoDeviceId @@ -83,6 +87,23 @@ export function ControlBar({ variation, controls, ...props }: ControlBarProps) { const [audioEnabled, setAudioEnabled] = useState(visibleControls.microphone ?? DEFAULT_USER_CHOICES.audioEnabled) const [audioDeviceId, setAudioDeviceId] = useState(initialAudioDeviceId) + const { enabled: isScreenShareEnabled, toggle: toggleScreenShare } = useTrackToggle({ + source: Track.Source.ScreenShare, + captureOptions: { audio: true, selfBrowserSurface: 'include' } + }) + + const handleOpen = () => { + if (!isScreenShareEnabled) { + toggleScreenShare() + } else { + setConfirmStopSharing(true) + } + } + + const handleClose = () => { + setConfirmStopSharing(false) + } + const tracks = usePreviewTracks( { audio: audioEnabled ? { deviceId: initialAudioDeviceId } : false, @@ -115,69 +136,89 @@ export function ControlBar({ variation, controls, ...props }: ControlBarProps) { return (
- {visibleControls.microphone && ( -
- setAudioEnabled(enabled)} - > - {showText && 'Microphone'} - -
- setAudioDeviceId(id)} - /> +
+ {visibleControls.microphone && ( +
+ setAudioEnabled(enabled)} + > + + +
+ setAudioDeviceId(id)} + /> +
-
- )} - {visibleControls.camera && ( -
- setVideoEnabled(enabled)} - > - {showText && 'Camera'} - -
- setVideoDeviceId(id)} - /> + )} + {visibleControls.camera && ( +
+ setVideoEnabled(enabled)} + > + + +
+ setVideoDeviceId(id)} + /> +
-
- )} - {visibleControls.screenShare && browserSupportsScreenSharing && ( - - {showText && (isScreenShareEnabled ? 'Stop screen share' : 'Share screen')} - - )} - {visibleControls.leave && ( - - {showIcon && } - {showText && 'Leave'} - - )} + )} + {visibleControls.screenShare && browserSupportsScreenSharing && ( + +
+ {t('control_bar.stop_sharing')} + + } + trigger={ + + } + basic + style={{ + backgroundColor: 'transparent', + padding: 0 + }} + /> + )} + {visibleControls.leave && ( + + {showIcon && } + {t('control_bar.leave')} + + )} +
{visibleControls.chat && ( - {showIcon && } + {showIcon && } {showText && 'Chat'} )} diff --git a/src/components/VideoConference/ControlBar/PeoplePanelToggleButton/PeoplePanelToggleButton.module.css b/src/components/VideoConference/ControlBar/PeoplePanelToggleButton/PeoplePanelToggleButton.module.css index 6997db6..9a93517 100644 --- a/src/components/VideoConference/ControlBar/PeoplePanelToggleButton/PeoplePanelToggleButton.module.css +++ b/src/components/VideoConference/ControlBar/PeoplePanelToggleButton/PeoplePanelToggleButton.module.css @@ -1,6 +1,8 @@ .button { + padding: 8px; + height: 48px; + width: 48px; background-color: transparent; - padding: 0.625rem 1rem; } .buttonActive { @@ -8,8 +10,8 @@ } .peopleIcon { - height: 18px; - width: 18px; + height: 32px; + width: 32px; } .label { diff --git a/src/components/VideoConference/ControlBar/PeoplePanelToggleButton/PeoplePanelToggleButton.tsx b/src/components/VideoConference/ControlBar/PeoplePanelToggleButton/PeoplePanelToggleButton.tsx index 9a2fce4..c5cf72d 100644 --- a/src/components/VideoConference/ControlBar/PeoplePanelToggleButton/PeoplePanelToggleButton.tsx +++ b/src/components/VideoConference/ControlBar/PeoplePanelToggleButton/PeoplePanelToggleButton.tsx @@ -1,8 +1,8 @@ import React, { useCallback, useMemo } from 'react' import classNames from 'classnames' import { Label } from 'decentraland-ui' -import PeopleIcon from '../../../../assets/icons/PeopleIcon' import { WidgetState, useLayoutContext } from '../../../../hooks/useLayoutContext' +import { PeopleIcon } from '../../../Icons' import type { Props } from './PeoplePanelToggleButton.types' import styles from './PeoplePanelToggleButton.module.css' diff --git a/src/components/VideoConference/RightPanel/Chat/Chat.tsx b/src/components/VideoConference/RightPanel/Chat/Chat.tsx index b5f1c5e..04385bd 100644 --- a/src/components/VideoConference/RightPanel/Chat/Chat.tsx +++ b/src/components/VideoConference/RightPanel/Chat/Chat.tsx @@ -4,10 +4,10 @@ import { useLocalParticipant } from '@livekit/components-react' import classNames from 'classnames' import { t } from 'decentraland-dapps/dist/modules/translation/utils' import { Button, Header, Input } from 'decentraland-ui' -import SendIcon from '../../../../assets/icons/SendIcon' import { useChat } from '../../../../hooks/useChat' import { useLayoutContext } from '../../../../hooks/useLayoutContext' import { cloneSingleChild } from '../../../../utils/chat' +import { SendIcon } from '../../../Icons' import ChatEntry from './ChatEntry' import { Props } from './Chat.types' import styles from './Chat.module.css' diff --git a/src/components/VideoConference/VideoConference/VideoConference.module.css b/src/components/VideoConference/VideoConference/VideoConference.module.css index aeb15f4..bda1f6b 100644 --- a/src/components/VideoConference/VideoConference/VideoConference.module.css +++ b/src/components/VideoConference/VideoConference/VideoConference.module.css @@ -5,6 +5,7 @@ .LayoutWrapper { flex-direction: row; padding: 8px 0px; + height: calc(100% - (var(--lk-control-bar-height) + var(--lk-control-bar-margin-top) + var(--lk-control-bar-margin-bottom))); } .GridLayout { diff --git a/src/index.css b/src/index.css index 2b3001b..8c3c0c1 100644 --- a/src/index.css +++ b/src/index.css @@ -139,5 +139,7 @@ code { } [data-lk-theme='default'] { - --lk-control-bar-height: 44px; + --lk-control-bar-height: 48px; + --lk-control-bar-margin-top: 18px; + --lk-control-bar-margin-bottom: 26px; } diff --git a/src/modules/translation/locales/en.json b/src/modules/translation/locales/en.json index 1da35ab..ad715d0 100644 --- a/src/modules/translation/locales/en.json +++ b/src/modules/translation/locales/en.json @@ -20,5 +20,9 @@ }, "chat_entry": { "you": "You" + }, + "control_bar": { + "leave": "Leave", + "stop_sharing": "Stop sharing" } } diff --git a/src/modules/translation/locales/es.json b/src/modules/translation/locales/es.json index ef942a0..140b7af 100644 --- a/src/modules/translation/locales/es.json +++ b/src/modules/translation/locales/es.json @@ -20,5 +20,9 @@ }, "chat_entry": { "you": "Tu" + }, + "control_bar": { + "leave": "Salir", + "stop_sharing": "Deja de compartir" } } diff --git a/src/modules/translation/locales/zh.json b/src/modules/translation/locales/zh.json index 61217ff..5eda17b 100644 --- a/src/modules/translation/locales/zh.json +++ b/src/modules/translation/locales/zh.json @@ -20,5 +20,9 @@ }, "chat_entry": { "you": "你" + }, + "control_bar": { + "leave": "离开", + "stop_sharing": "停止共享" } }