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
This commit is contained in:
Gabe Kangas
2023-05-22 18:56:44 -07:00
committed by GitHub
parent b9b569f3fe
commit 69f217f758
21 changed files with 945 additions and 215 deletions

View File

@@ -67,7 +67,7 @@ const ActionButtons: FC<ActionButtonProps> = ({
)}
</ActionButtonRow>
</div>
<div className={styles.mobileActionButtonMenu}>
<div className={styles.mobileActionButtons}>
{(supportsBrowserNotifications ||
supportsBrowserNotifications ||
externalActionButtons.length > 0) && (

View File

@@ -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;
}

View File

@@ -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<AppStateOptions>(appStateAtom);
const clientConfig = useRecoilValue<ClientConfig>(clientConfigStateAtom);
const isChatVisible = useRecoilValue<boolean>(isChatVisibleSelector);
const isChatAvailable = useRecoilValue<boolean>(isChatAvailableSelector);
const currentUser = useRecoilValue(currentUserAtom);
const serverStatus = useRecoilValue<ServerStatus>(serverStatusState);
const [isMobile, setIsMobile] = useRecoilState<boolean | undefined>(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)}
/>
</Modal>
{showChatModal && isChatVisible && (
<ChatModal
messages={messages}
currentUser={currentUser}
handleClose={() => setShowChatModal(false)}
/>
)}
{isChatVisible && (
<Button
id="mobile-chat-button"
type="primary"
onClick={() => setShowChatModal(true)}
className={styles.floatingMobileChatModalButton}
style={{ zIndex: 99 }}
>
Chat <MessageFilled style={{ transform: 'translateX(-1px)' }} />
</Button>
)}
</>
);
};

View File

@@ -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 }) => (
<ComponentError
message={error}
@@ -64,32 +44,12 @@ const ComponentErrorFallback = ({ error, resetErrorBoundary }) => (
/>
);
const ChatContent: FC<ChatContentProps> = ({ showChat, chatEnabled, messages, currentUser }) => {
const { id, displayName } = currentUser;
return showChat && !!currentUser ? (
<ChatContainer
messages={messages}
usernameToHighlight={displayName}
chatUserId={id}
isModerator={false}
chatAvailable={chatEnabled}
/>
) : (
<Skeleton loading active paragraph={{ rows: 7 }} />
);
};
export const MobileContent: FC<MobileContentProps> = ({
name,
summary,
tags,
socialHandles,
extraPageContent,
messages,
currentUser,
showChat,
chatEnabled,
setShowFollowModal,
supportFediverseFeatures,
online,
@@ -111,23 +71,10 @@ export const MobileContent: FC<MobileContentProps> = ({
);
const items = [];
if (showChat && currentUser) {
items.push({
label: 'Chat',
key: '0',
children: (
<ChatContent
showChat={showChat}
chatEnabled={chatEnabled}
messages={messages}
currentUser={currentUser}
/>
),
});
}
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 (