From 69f217f758796ca4ae16d4a070c3b38e7ba12fb9 Mon Sep 17 00:00:00 2001 From: Gabe Kangas Date: Mon, 22 May 2023 18:56:44 -0700 Subject: [PATCH] Refactor mobile chat into modal (#3038) * feat(mobile): refactor mobile chat into modal - Make page always scrollable - Move mobile chat into a standalone modal * fix(test): split out mobile browser test specs * fix(mobile): force chat button to render on top of footer * fix: some small updates from review * fix: hide/show hide chat menu option based on width * fix: chat button icon getting cut off * chore(tests): add browser tests for mobile chat modal * chore(tests): add story for ChatModal component * fix(test): quiet shellcheck * fix: remove unused import * fix(tests): silence storybook linting warning * fix(ui): reposition chat modal button icon with transform --- .../e2e/offline/06_offline_mobile.cy.js | 16 + .../cypress/e2e/online/01_online_live.cy.js | 120 ++-- .../e2e/online/06_online_mobile_live.cy.js | 40 ++ test/automated/browser/run.sh | 10 +- test/automated/screenshots/run.sh | 2 + .../ActionButtonMenu.module.scss | 1 - .../chat/ChatContainer/ChatContainer.tsx | 4 +- .../chat/ChatTextField/ChatTextField.tsx | 7 +- .../UserDropdown/UserDropdown.module.scss | 35 +- .../UserDropdown/UserDropdown.stories.tsx | 2 +- .../common/UserDropdown/UserDropdown.tsx | 30 +- web/components/layouts/Main/Main.module.scss | 2 +- web/components/layouts/Main/Main.tsx | 8 +- .../modals/ChatModal/ChatModal.module.scss | 12 + .../modals/ChatModal/ChatModal.stories.tsx | 598 ++++++++++++++++++ web/components/modals/ChatModal/ChatModal.tsx | 66 ++ web/components/ui/Content/ActionButtons.tsx | 2 +- web/components/ui/Content/Content.module.scss | 34 +- web/components/ui/Content/Content.tsx | 30 +- web/components/ui/Content/MobileContent.tsx | 61 +- web/components/ui/Header/Header.tsx | 80 ++- 21 files changed, 945 insertions(+), 215 deletions(-) create mode 100644 test/automated/browser/cypress/e2e/offline/06_offline_mobile.cy.js create mode 100644 test/automated/browser/cypress/e2e/online/06_online_mobile_live.cy.js create mode 100644 web/components/modals/ChatModal/ChatModal.module.scss create mode 100644 web/components/modals/ChatModal/ChatModal.stories.tsx create mode 100644 web/components/modals/ChatModal/ChatModal.tsx diff --git a/test/automated/browser/cypress/e2e/offline/06_offline_mobile.cy.js b/test/automated/browser/cypress/e2e/offline/06_offline_mobile.cy.js new file mode 100644 index 000000000..6ef309b81 --- /dev/null +++ b/test/automated/browser/cypress/e2e/offline/06_offline_mobile.cy.js @@ -0,0 +1,16 @@ +import { setup } from '../../support/setup.js'; +import filterTests from '../../support/filterTests'; + +setup(); + +filterTests(['mobile'], () => { + describe(`Live mobile tests`, () => { + it('Can visit the page', () => { + cy.visit('http://localhost:8080'); + }); + + it('Mobile chat button should not be visible', () => { + cy.get('#mobile-chat-button').should('not.exist'); + }); + }); +}); diff --git a/test/automated/browser/cypress/e2e/online/01_online_live.cy.js b/test/automated/browser/cypress/e2e/online/01_online_live.cy.js index 65f56053b..8e945aad0 100644 --- a/test/automated/browser/cypress/e2e/online/01_online_live.cy.js +++ b/test/automated/browser/cypress/e2e/online/01_online_live.cy.js @@ -1,5 +1,6 @@ import { setup } from '../../support/setup.js'; import fetchData from '../../support/fetchData.js'; +import filterTests from '../../support/filterTests'; setup(); @@ -12,73 +13,60 @@ describe(`Live tests`, () => { cy.get('.vjs-big-play-button').should('be.visible'); }); - // it('Chat should be visible', () => { - // cy.get('#chat-container').should('be.visible'); - // }); - it('User menu should be visible', () => { cy.get('#user-menu').should('be.visible'); }); - - // it('Chat join message should exist', () => { - // cy.contains('joined the chat').should('be.visible'); - // }); - - it('User menu should be visible', () => { - cy.get('#user-menu').should('be.visible'); - }); - - it('Click on user menu', () => { - cy.get('#user-menu').click(); - }); - - it('Can toggle chat off', () => { - cy.contains('Hide Chat').click(); - }); - - it('Chat should not be visible', () => { - cy.get('#chat-container').should('not.exist'); - }); - - it('Click on user menu', () => { - cy.get('#user-menu').click(); - }); - - it('Can toggle chat on', () => { - cy.contains('Show Chat').click(); - }); - - // it('Chat should be re-visible', () => { - // cy.get('#chat-container').should('be.visible'); - // }); - - it('Click on user menu', () => { - cy.get('#user-menu').click(); - }); - - it('Show change name modal', () => { - cy.contains('Change name').click(); - }); - - it('Should change name', () => { - cy.get('#name-change-field').focus(); - cy.get('#name-change-field').type('{ctrl+a}'); - cy.get('#name-change-field').type('my-new-name'); - cy.get('#name-change-submit').click(); - cy.get('.ant-modal-close-x').click(); - cy.wait(1500); - // cy.contains('is now known as').should('be.visible'); - }); - - it('Should change to custom websocket host', () => { - fetchData('http://localhost:8080/api/admin/config/sockethostoverride', { - method: 'POST', - data: { value: 'ws://localhost:8080' }, - }); - cy.wait(1500); - }); - - it('Refresh page with new socket host', () => { - cy.visit('http://localhost:8080'); - }); +}); + +filterTests(['desktop'], () => { + describe(`Live desktop tests`, () => { + it('Click on user menu', () => { + cy.get('#user-menu').click(); + }); + it('Can toggle chat off', () => { + cy.contains('Hide Chat').click(); + }); + + it('Chat should not be visible', () => { + cy.get('#chat-container').should('not.exist'); + }); + + it('Click on user menu', () => { + cy.get('#user-menu').click(); + }); + + it('Can toggle chat on', () => { + cy.contains('Show Chat').click(); + }); + + it('Click on user menu', () => { + cy.get('#user-menu').click(); + }); + + it('Show change name modal', () => { + cy.contains('Change name').click(); + }); + + it('Should change name', () => { + cy.get('#name-change-field').focus(); + cy.get('#name-change-field').type('{ctrl+a}'); + cy.get('#name-change-field').type('my-new-name'); + cy.get('#name-change-submit').click(); + cy.get('.ant-modal-close-x').click(); + cy.wait(1500); + // cy.contains('is now known as').should('be.visible'); + }); + + it('Should change to custom websocket host', () => { + fetchData('http://localhost:8080/api/admin/config/sockethostoverride', { + method: 'POST', + data: { value: 'ws://localhost:8080' }, + }); + cy.wait(1500); + }); + + it('Refresh page with new socket host', () => { + cy.visit('http://localhost:8080'); + }); + }); }); diff --git a/test/automated/browser/cypress/e2e/online/06_online_mobile_live.cy.js b/test/automated/browser/cypress/e2e/online/06_online_mobile_live.cy.js new file mode 100644 index 000000000..03721e2ba --- /dev/null +++ b/test/automated/browser/cypress/e2e/online/06_online_mobile_live.cy.js @@ -0,0 +1,40 @@ +import { setup } from '../../support/setup.js'; +import filterTests from '../../support/filterTests'; + +setup(); + +filterTests(['mobile'], () => { + describe(`Live mobile tests`, () => { + it('Can visit the page', () => { + cy.visit('http://localhost:8080'); + }); + + it('Mobile chat button should be visible', () => { + cy.get('#mobile-chat-button').should('be.visible'); + }); + + it('Click mobile chat button', () => { + cy.get('#mobile-chat-button').click(); + }); + + it('Mobile chat modal should be visible', () => { + cy.get('.ant-modal').should('be.visible'); + }); + + it('Chat container should be visible', () => { + cy.get('#chat-container').should('be.visible'); + }); + + it('Chat input should be visible', () => { + cy.get('#chat-input').should('be.visible'); + }); + + it('Click on user menu', () => { + cy.get('#chat-modal-user-menu').click(); + }); + + it('Show change name modal', () => { + cy.contains('Change name').click(); + }); + }); +}); diff --git a/test/automated/browser/run.sh b/test/automated/browser/run.sh index 662ae961c..a4b738dac 100755 --- a/test/automated/browser/run.sh +++ b/test/automated/browser/run.sh @@ -15,7 +15,6 @@ else echo "Google Chrome not found. Using Electron." fi - # Bundle the updated web code into the server codebase. if [ -z "$SKIP_BUILD" ]; then echo "Bundling web code into server..." @@ -30,7 +29,6 @@ else echo "Skipping web build..." fi - # Install the web test framework if [ -z "$SKIP_BUILD" ]; then echo "Installing test dependencies..." @@ -47,13 +45,13 @@ install_ffmpeg start_owncast # Run cypress browser tests for desktop -npx cypress run --browser "$BROWSER" --group "desktop-offline" --env tags=desktop --ci-build-id $BUILD_ID --tag "desktop,offline" --record --key e9c8b547-7a8f-452d-8c53-fd7531491e3b --spec "cypress/e2e/offline/*.cy.js" +npx cypress run --parallel --browser "$BROWSER" --group "desktop-offline" --env tags=desktop --ci-build-id $BUILD_ID --tag "desktop,offline" --record --key e9c8b547-7a8f-452d-8c53-fd7531491e3b --spec "cypress/e2e/offline/*.cy.js" # Run cypress browser tests for mobile -npx cypress run --browser "$BROWSER" --group "mobile-offline" --ci-build-id $BUILD_ID --tag "mobile,offline" --record --key e9c8b547-7a8f-452d-8c53-fd7531491e3b --spec "cypress/e2e/offline/*.cy.js" --config viewportWidth=375,viewportHeight=667 +npx cypress run --parallel --browser "$BROWSER" --group "mobile-offline" --env tags=mobile --ci-build-id $BUILD_ID --tag "mobile,offline" --record --key e9c8b547-7a8f-452d-8c53-fd7531491e3b --spec "cypress/e2e/offline/*.cy.js" --config viewportWidth=375,viewportHeight=667 start_stream # Run cypress browser tests for desktop -npx cypress run --browser "$BROWSER" --group "desktop-online" --env tags=desktop --ci-build-id $BUILD_ID --tag "desktop,online" --record --key e9c8b547-7a8f-452d-8c53-fd7531491e3b --spec "cypress/e2e/online/*.cy.js" +npx cypress run --parallel --browser "$BROWSER" --group "desktop-online" --env tags=desktop --ci-build-id $BUILD_ID --tag "desktop,online" --record --key e9c8b547-7a8f-452d-8c53-fd7531491e3b --spec "cypress/e2e/online/*.cy.js" # Run cypress browser tests for mobile -npx cypress run --browser "$BROWSER" --group "mobile-online" --ci-build-id $BUILD_ID --tag "mobile,online" --record --key e9c8b547-7a8f-452d-8c53-fd7531491e3b --spec "cypress/e2e/online/*.cy.js" --config viewportWidth=375,viewportHeight=667 +npx cypress run --parallel --browser "$BROWSER" --group "mobile-online" --env tags=mobile --ci-build-id $BUILD_ID --tag "mobile,online" --record --key e9c8b547-7a8f-452d-8c53-fd7531491e3b --spec "cypress/e2e/online/*.cy.js" --config viewportWidth=375,viewportHeight=667 diff --git a/test/automated/screenshots/run.sh b/test/automated/screenshots/run.sh index a9309da03..0fcc6ef67 100755 --- a/test/automated/screenshots/run.sh +++ b/test/automated/screenshots/run.sh @@ -5,7 +5,9 @@ set -o errexit set -o pipefail finish() { + # shellcheck disable=SC2317 kill_with_kids "$BROWSERSTACK_PID" + # shellcheck disable=SC2317 kill_with_kids "$STREAM_PID" } diff --git a/web/components/action-buttons/ActionButtonMenu/ActionButtonMenu.module.scss b/web/components/action-buttons/ActionButtonMenu/ActionButtonMenu.module.scss index 9054d4828..4d369062f 100644 --- a/web/components/action-buttons/ActionButtonMenu/ActionButtonMenu.module.scss +++ b/web/components/action-buttons/ActionButtonMenu/ActionButtonMenu.module.scss @@ -7,7 +7,6 @@ display: flex; align-items: center; height: 100%; - padding-bottom: 16px; // to match antd nav margin-bottom button { height: 100%; diff --git a/web/components/chat/ChatContainer/ChatContainer.tsx b/web/components/chat/ChatContainer/ChatContainer.tsx index d5758c60b..9d660fe60 100644 --- a/web/components/chat/ChatContainer/ChatContainer.tsx +++ b/web/components/chat/ChatContainer/ChatContainer.tsx @@ -29,6 +29,7 @@ export type ChatContainerProps = { showInput?: boolean; height?: string; chatAvailable: boolean; + focusInput?: boolean; }; function shouldCollapseMessages( @@ -93,6 +94,7 @@ export const ChatContainer: FC = ({ showInput, height, chatAvailable: chatEnabled, + focusInput = true, }) => { const [showScrollToBottomButton, setShowScrollToBottomButton] = useState(false); const [isAtBottom, setIsAtBottom] = useState(false); @@ -282,7 +284,7 @@ export const ChatContainer: FC = ({ {MessagesTable} {showInput && (
- +
)} diff --git a/web/components/chat/ChatTextField/ChatTextField.tsx b/web/components/chat/ChatTextField/ChatTextField.tsx index f33da3fc3..fe79c29cc 100644 --- a/web/components/chat/ChatTextField/ChatTextField.tsx +++ b/web/components/chat/ChatTextField/ChatTextField.tsx @@ -131,11 +131,12 @@ const getCharacterCount = node => { export type ChatTextFieldProps = { defaultText?: string; enabled: boolean; + focusInput: boolean; }; const characterLimit = 300; -export const ChatTextField: FC = ({ defaultText, enabled }) => { +export const ChatTextField: FC = ({ defaultText, enabled, focusInput }) => { const [showEmojis, setShowEmojis] = useState(false); const [characterCount, setCharacterCount] = useState(defaultText?.length); const websocketService = useRecoilValue(websocketServiceAtom); @@ -240,7 +241,7 @@ export const ChatTextField: FC = ({ defaultText, enabled }) }; return ( -
+
= ({ defaultText, enabled }) style={{ width: '100%' }} role="textbox" aria-label="Chat text input" - autoFocus + autoFocus={focusInput} /> { [], ); - return ; + return ; }; const Template: ComponentStory = args => ( diff --git a/web/components/common/UserDropdown/UserDropdown.tsx b/web/components/common/UserDropdown/UserDropdown.tsx index 78d06714d..494c7f826 100644 --- a/web/components/common/UserDropdown/UserDropdown.tsx +++ b/web/components/common/UserDropdown/UserDropdown.tsx @@ -1,4 +1,5 @@ import { Menu, Dropdown, Button } from 'antd'; +import classnames from 'classnames'; import { useRecoilState, useRecoilValue } from 'recoil'; import { FC, useState } from 'react'; @@ -55,16 +56,29 @@ const AuthModal = dynamic( ); export type UserDropdownProps = { + id: string; username?: string; + hideTitleOnMobile?: boolean; + showToggleChatOption?: boolean; }; -export const UserDropdown: FC = ({ username: defaultUsername = undefined }) => { +export const UserDropdown: FC = ({ + id, + username: defaultUsername = undefined, + hideTitleOnMobile = false, + showToggleChatOption: showHideChatOption = true, +}) => { const [showNameChangeModal, setShowNameChangeModal] = useState(false); const [showAuthModal, setShowAuthModal] = useState(false); const [chatToggleVisible, setChatToggleVisible] = useRecoilState(chatVisibleToggleAtom); const appState = useRecoilValue(appStateAtom); const toggleChatVisibility = () => { + // If we don't support the hide chat option then don't do anything. + if (!showHideChatOption) { + return; + } + setChatToggleVisible(!chatToggleVisible); }; @@ -97,12 +111,13 @@ export const UserDropdown: FC = ({ username: defaultUsername } onClick={() => setShowAuthModal(true)}> Authenticate - {appState.chatAvailable && ( + {showHideChatOption && appState.chatAvailable && ( } onClick={() => toggleChatVisibility()} aria-expanded={chatToggleVisible} + className={styles.chatToggle} > {chatToggleVisible ? 'Hide Chat' : 'Show Chat'} @@ -121,10 +136,17 @@ export const UserDropdown: FC = ({ username: defaultUsername /> )} > -
+
diff --git a/web/components/layouts/Main/Main.module.scss b/web/components/layouts/Main/Main.module.scss index 1187dbc59..0c8e65900 100644 --- a/web/components/layouts/Main/Main.module.scss +++ b/web/components/layouts/Main/Main.module.scss @@ -4,7 +4,7 @@ // this margin is for fixed header padding-top: var(--header-height); background-color: var(--theme-color-main-background); - min-height: 100dvh; + min-height: 100vh; position: relative; diff --git a/web/components/layouts/Main/Main.tsx b/web/components/layouts/Main/Main.tsx index 40aaf7a1c..a71bee8df 100644 --- a/web/components/layouts/Main/Main.tsx +++ b/web/components/layouts/Main/Main.tsx @@ -171,11 +171,9 @@ export const Main: FC = () => { )} - {(!isMobile || !online) && ( -
-
-
- )} +
+
+
-
+
{(supportsBrowserNotifications || supportsBrowserNotifications || externalActionButtons.length > 0) && ( diff --git a/web/components/ui/Content/Content.module.scss b/web/components/ui/Content/Content.module.scss index e4912e5c7..856e38ea5 100644 --- a/web/components/ui/Content/Content.module.scss +++ b/web/components/ui/Content/Content.module.scss @@ -13,20 +13,9 @@ bottom: 0; width: 100%; - @include screen(tablet) { - top: 0; + @include screen(tablet) { + top: 0; position: relative; - - &.online { - position: absolute; - top: calc(var(--player-container-height) + var(--status-bar-height) + var(--header-height)); - - // As we want content in the tabs to scroll within itself, force the tabs to display max height at all times. - // (We don't have to do this when not-online because we're having the entire layout scroll. - :global(.ant-tabs-content) { - height: 100% !important; - } - } } :global(.ant-tabs-nav) { @@ -53,16 +42,22 @@ color: var(--theme-color-background-main); } -.mobileActionButtonMenu { +.mobileActionButtons { display: none; @include screen(tablet) { - display: block; + display: flex; + align-items: center; position: absolute; top: 4px; right: 10px; z-index: 199; } + + > * { + margin-left: 5px; + margin-right: 5px; + } } .desktopActionButtons { @@ -109,3 +104,12 @@ .offlineBanner { color: var(--theme-color-background-main); } + +.floatingMobileChatModalButton { + position: fixed; + width: 100px; + height: 40px; + bottom: 40px; + right: 10px; + font-weight: 600; +} diff --git a/web/components/ui/Content/Content.tsx b/web/components/ui/Content/Content.tsx index 4fc43173e..43c932df8 100644 --- a/web/components/ui/Content/Content.tsx +++ b/web/components/ui/Content/Content.tsx @@ -1,5 +1,6 @@ import { useRecoilState, useRecoilValue } from 'recoil'; -import { Skeleton, Col, Row } from 'antd'; +import { Skeleton, Col, Row, Button } from 'antd'; +import MessageFilled from '@ant-design/icons/MessageFilled'; import { FC, useEffect, useState } from 'react'; import dynamic from 'next/dynamic'; import classnames from 'classnames'; @@ -11,7 +12,6 @@ import { clientConfigStateAtom, chatMessagesAtom, currentUserAtom, - isChatAvailableSelector, isChatVisibleSelector, appStateAtom, isOnlineSelector, @@ -32,6 +32,7 @@ import { ExternalAction } from '../../../interfaces/external-action'; import { Modal } from '../Modal/Modal'; import { DesktopContent } from './DesktopContent'; import { MobileContent } from './MobileContent'; +import { ChatModal } from '../../modals/ChatModal/ChatModal'; // Lazy loaded components @@ -91,7 +92,6 @@ export const Content: FC = () => { const appState = useRecoilValue(appStateAtom); const clientConfig = useRecoilValue(clientConfigStateAtom); const isChatVisible = useRecoilValue(isChatVisibleSelector); - const isChatAvailable = useRecoilValue(isChatAvailableSelector); const currentUser = useRecoilValue(currentUserAtom); const serverStatus = useRecoilValue(serverStatusState); const [isMobile, setIsMobile] = useRecoilState(isMobileAtom); @@ -124,6 +124,8 @@ export const Content: FC = () => { const [supportsBrowserNotifications, setSupportsBrowserNotifications] = useState(false); const supportFediverseFeatures = fediverseEnabled; + const [showChatModal, setShowChatModal] = useState(false); + const externalActionSelected = (action: ExternalAction) => { const { openExternally, url } = action; // apply openExternally only if we don't have an HTML embed @@ -262,12 +264,8 @@ export const Content: FC = () => { tags={tags} socialHandles={socialHandles} extraPageContent={extraPageContent} - messages={messages} - currentUser={currentUser} - showChat={showChat} setShowFollowModal={setShowFollowModal} supportFediverseFeatures={supportFediverseFeatures} - chatEnabled={isChatAvailable} online={online} /> ) : ( @@ -305,6 +303,24 @@ export const Content: FC = () => { handleClose={() => setShowFollowModal(false)} /> + {showChatModal && isChatVisible && ( + setShowChatModal(false)} + /> + )} + {isChatVisible && ( + + )} ); }; diff --git a/web/components/ui/Content/MobileContent.tsx b/web/components/ui/Content/MobileContent.tsx index 81c7b4bc3..851525226 100644 --- a/web/components/ui/Content/MobileContent.tsx +++ b/web/components/ui/Content/MobileContent.tsx @@ -1,14 +1,12 @@ import React, { ComponentType, FC } from 'react'; import dynamic from 'next/dynamic'; -import { Skeleton, TabsProps } from 'antd'; +import { TabsProps } from 'antd'; import { ErrorBoundary } from 'react-error-boundary'; import classNames from 'classnames'; import { SocialLink } from '../../../interfaces/social-link.model'; import styles from './Content.module.scss'; import { CustomPageContent } from '../CustomPageContent/CustomPageContent'; import { ContentHeader } from '../../common/ContentHeader/ContentHeader'; -import { ChatMessage } from '../../../interfaces/chat-message.model'; -import { CurrentUser } from '../../../interfaces/current-user'; import { ComponentError } from '../ComponentError/ComponentError'; export type MobileContentProps = { @@ -19,10 +17,6 @@ export type MobileContentProps = { extraPageContent: string; setShowFollowModal: (show: boolean) => void; supportFediverseFeatures: boolean; - messages: ChatMessage[]; - currentUser: CurrentUser; - showChat: boolean; - chatEnabled: boolean; online: boolean; }; @@ -42,20 +36,6 @@ const FollowerCollection = dynamic( }, ); -const ChatContainer = dynamic( - () => import('../../chat/ChatContainer/ChatContainer').then(mod => mod.ChatContainer), - { - ssr: false, - }, -); - -type ChatContentProps = { - showChat: boolean; - chatEnabled: boolean; - messages: ChatMessage[]; - currentUser: CurrentUser; -}; - const ComponentErrorFallback = ({ error, resetErrorBoundary }) => ( ( /> ); -const ChatContent: FC = ({ showChat, chatEnabled, messages, currentUser }) => { - const { id, displayName } = currentUser; - - return showChat && !!currentUser ? ( - - ) : ( - - ); -}; - export const MobileContent: FC = ({ name, summary, tags, socialHandles, extraPageContent, - messages, - currentUser, - showChat, - chatEnabled, setShowFollowModal, supportFediverseFeatures, online, @@ -111,23 +71,10 @@ export const MobileContent: FC = ({ ); const items = []; - if (showChat && currentUser) { - items.push({ - label: 'Chat', - key: '0', - children: ( - - ), - }); - } - items.push({ label: 'About', key: '2', children: aboutTabContent }); + + items.push({ label: 'About', key: '0', children: aboutTabContent }); if (supportFediverseFeatures) { - items.push({ label: 'Followers', key: '3', children: followersTabContent }); + items.push({ label: 'Followers', key: '1', children: followersTabContent }); } return ( diff --git a/web/components/ui/Header/Header.tsx b/web/components/ui/Header/Header.tsx index adbeae80b..5b294e688 100644 --- a/web/components/ui/Header/Header.tsx +++ b/web/components/ui/Header/Header.tsx @@ -1,5 +1,5 @@ import { Tooltip, Avatar } from 'antd'; -import { FC } from 'react'; +import { FC, useEffect, useState } from 'react'; import cn from 'classnames'; import dynamic from 'next/dynamic'; import Link from 'next/link'; @@ -21,41 +21,51 @@ export type HeaderComponentProps = { online: boolean; }; -export const Header: FC = ({ name, chatAvailable, chatDisabled, online }) => ( -
- {online ? ( - - Skip to player +export const Header: FC = ({ name, chatAvailable, chatDisabled, online }) => { + const [canHideChat, setCanHideChat] = useState(false); + + useEffect(() => { + setCanHideChat(window.innerWidth >= 768); + }, []); + + return ( +
+ {online ? ( + + Skip to player + + ) : ( + + Skip to offline message + + )} + + Skip to page content - ) : ( - - Skip to offline message + + Skip to footer - )} - - Skip to page content - - - Skip to footer - -
- - {chatAvailable && !chatDisabled && } - {!chatAvailable && !chatDisabled && ( - - Chat is offline - - )} -
-); + {chatAvailable && !chatDisabled && ( + + )} + {!chatAvailable && !chatDisabled && ( + + Chat is offline + + )} +
+ ); +}; export default Header;