* fix: prevent chat container from overflowing viewport in embedded view Add max-height: 100vh to .chatContainer to prevent the chat from getting cut off vertically when used in the /embed/chat/readwrite view. Fixes #4492 * fix: use flexbox layout for readwrite embed to prevent overflow in short viewports Changes the readwrite embed to use a flexbox container with height: 100dvh instead of a fixed 92vh height on the chat. This ensures the header takes its natural height and the chat fills the remaining space without overflow. - Add .embed-container with flex column layout and 100dvh height - Set #chat-container to flex: 1 with min-height: 0 to fill remaining space - Change ChatContainer height prop from "92vh" to "100%" This addresses maintainer feedback that the chat was still getting cut off in shorter viewports. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * refactor: remove max-height from global ChatContainer styles Address Copilot review feedback: the max-height: 100vh on the global .chatContainer class is no longer needed since the embed-container flexbox layout properly constrains the height for the embedded chat. Removing this avoids potential unintended side effects on other ChatContainer instances (desktop sidebar, chat modal). Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
124 lines
4.4 KiB
TypeScript
124 lines
4.4 KiB
TypeScript
/* eslint-disable react/no-unknown-property */
|
|
import { useRecoilValue } from 'recoil';
|
|
import { useEffect } from 'react';
|
|
import { ErrorBoundary } from 'react-error-boundary';
|
|
import { useTranslation } from 'next-export-i18n';
|
|
import { ChatMessage } from '../../../../interfaces/chat-message.model';
|
|
import { ChatContainer } from '../../../../components/chat/ChatContainer/ChatContainer';
|
|
import {
|
|
ClientConfigStore,
|
|
currentUserAtom,
|
|
visibleChatMessagesSelector,
|
|
clientConfigStateAtom,
|
|
appStateAtom,
|
|
serverStatusState,
|
|
isChatAvailableSelector,
|
|
chatAuthenticatedAtom,
|
|
} from '../../../../components/stores/ClientConfigStore';
|
|
import Header from '../../../../components/ui/Header/Header';
|
|
import { ClientConfig } from '../../../../interfaces/client-config.model';
|
|
import { AppStateOptions } from '../../../../components/stores/application-state';
|
|
import { ServerStatus } from '../../../../interfaces/server-status.model';
|
|
import { Theme } from '../../../../components/theme/Theme';
|
|
import { ComponentError } from '../../../../components/ui/ComponentError/ComponentError';
|
|
import { Localization } from '../../../../types/localization';
|
|
|
|
export default function ReadWriteChatEmbed() {
|
|
const { t } = useTranslation();
|
|
const currentUser = useRecoilValue(currentUserAtom);
|
|
const messages = useRecoilValue<ChatMessage[]>(visibleChatMessagesSelector);
|
|
const clientConfig = useRecoilValue<ClientConfig>(clientConfigStateAtom);
|
|
const clientStatus = useRecoilValue<ServerStatus>(serverStatusState);
|
|
|
|
const appState = useRecoilValue<AppStateOptions>(appStateAtom);
|
|
const isChatAvailable = useRecoilValue(isChatAvailableSelector);
|
|
const isUserAuthenticated = useRecoilValue<boolean>(chatAuthenticatedAtom);
|
|
|
|
const { name, chatDisabled, chatRequireAuthentication } = clientConfig;
|
|
|
|
// Determine if chat input should be enabled based on authentication requirements.
|
|
// Moderators bypass the authentication requirement.
|
|
const chatInputEnabled = !!(
|
|
isChatAvailable &&
|
|
(!chatRequireAuthentication || isUserAuthenticated || currentUser?.isModerator)
|
|
);
|
|
const chatInputDisabledMessage = chatRequireAuthentication
|
|
? t(Localization.Frontend.Chat.authenticateToChat)
|
|
: t(Localization.Frontend.chatDisabled);
|
|
const { videoAvailable } = appState;
|
|
const { streamTitle, online } = clientStatus;
|
|
|
|
const headerText = online ? streamTitle || name : name;
|
|
|
|
// This is a hack to force a specific body background color for just this page.
|
|
useEffect(() => {
|
|
document.body.classList.add('body-background');
|
|
}, []);
|
|
|
|
return (
|
|
<div>
|
|
<style jsx global>
|
|
{`
|
|
.body-background {
|
|
background: var(--theme-color-components-chat-background);
|
|
}
|
|
.embed-container {
|
|
display: flex;
|
|
flex-direction: column;
|
|
height: 100vh;
|
|
height: 100dvh;
|
|
overflow: hidden;
|
|
}
|
|
.embed-container > #chat-container {
|
|
flex: 1;
|
|
min-height: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
.embed-container #chat-container #chat-container {
|
|
flex: 1 1 0;
|
|
min-height: 0;
|
|
}
|
|
.embed-container #chat-container #virtuoso {
|
|
flex: 1;
|
|
min-height: 0;
|
|
height: auto !important;
|
|
}
|
|
`}
|
|
</style>
|
|
<ErrorBoundary
|
|
// eslint-disable-next-line react/no-unstable-nested-components
|
|
fallbackRender={({ error }) => (
|
|
<ComponentError componentName="ReadWriteChatEmbed" message={error.message} />
|
|
)}
|
|
>
|
|
<div className="embed-container">
|
|
<ClientConfigStore />
|
|
<Theme />
|
|
<Header
|
|
name={headerText}
|
|
chatAvailable
|
|
chatDisabled={chatDisabled}
|
|
online={videoAvailable}
|
|
/>
|
|
{currentUser && (
|
|
<div id="chat-container">
|
|
<ChatContainer
|
|
messages={messages}
|
|
usernameToHighlight={currentUser.displayName}
|
|
chatUserId={currentUser.id}
|
|
isModerator={currentUser.isModerator}
|
|
showInput
|
|
height="100%"
|
|
chatAvailable={isChatAvailable}
|
|
inputEnabled={chatInputEnabled}
|
|
inputDisabledPlaceholder={chatInputDisabledMessage}
|
|
/>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</ErrorBoundary>
|
|
</div>
|
|
);
|
|
}
|