diff --git a/react/features/chat/components/web/Chat.tsx b/react/features/chat/components/web/Chat.tsx index 76587c0b3a59..2cf1b39c787f 100644 --- a/react/features/chat/components/web/Chat.tsx +++ b/react/features/chat/components/web/Chat.tsx @@ -114,6 +114,8 @@ const useStyles = makeStyles<{ _isResizing: boolean; width: number; }>()((theme, alignSelf: "center", borderColor: "#474747", borderWidth: "1px", + animation: "chatSlideIn 0.3s cubic-bezier(0.4, 0.0, 0.2, 1) forwards", + transformOrigin: "right center", "&:hover, &:focus-within": { "& .dragHandleContainer": { visibility: "visible", @@ -130,12 +132,65 @@ const useStyles = makeStyles<{ _isResizing: boolean; width: number; }>()((theme, width: "auto", borderRadius: 0, margin: 0, + animation: "chatSlideInMobile 0.3s cubic-bezier(0.4, 0.0, 0.2, 1) forwards", }, "*": { userSelect: "text", "-webkit-user-select": "text", }, + + "@keyframes chatSlideIn": { + "0%": { + opacity: 0, + transform: "translateX(100%) scale(0.95)", + }, + "100%": { + opacity: 1, + transform: "translateX(0) scale(1)", + }, + }, + + "@keyframes chatSlideInMobile": { + "0%": { + opacity: 0, + transform: "translateY(100%)", + }, + "100%": { + opacity: 1, + transform: "translateY(0)", + }, + }, + + "@keyframes chatSlideOut": { + "0%": { + opacity: 1, + transform: "translateX(0) scale(1)", + }, + "100%": { + opacity: 0, + transform: "translateX(100%) scale(0.95)", + }, + }, + + "@keyframes chatSlideOutMobile": { + "0%": { + opacity: 1, + transform: "translateY(0)", + }, + "100%": { + opacity: 0, + transform: "translateY(100%)", + }, + }, + }, + + containerClosing: { + animation: "chatSlideOut 0.25s cubic-bezier(0.4, 0.0, 0.6, 1) forwards", + + "@media (max-width: 580px)": { + animation: "chatSlideOutMobile 0.25s cubic-bezier(0.4, 0.0, 0.6, 1) forwards", + }, }, chatHeader: { @@ -259,6 +314,8 @@ const Chat = ({ const [ mousePosition, setMousePosition ] = useState(null); const [ dragChatWidth, setDragChatWidth ] = useState(null); const [showBanner, setShowBanner] = useState(true); + const [isClosing, setIsClosing] = useState(false); + const [shouldRender, setShouldRender] = useState(_isOpen); const maxChatWidth = useSelector(getChatMaxSize); const notifyTimestamp = useSelector((state: IReduxState) => state['features/chat'].notifyPrivateRecipientsChangedTimestamp @@ -270,6 +327,21 @@ const Chat = ({ const participants = useSelector(getRemoteParticipants); const isPrivateChatAllowed = useSelector((state: IReduxState) => isPrivateChatEnabledSelf(state)); + useEffect(() => { + if (_isOpen) { + setShouldRender(true); + setIsClosing(false); + } else if (shouldRender) { + setIsClosing(true); + const timer = setTimeout(() => { + setShouldRender(false); + setIsClosing(false); + }, 250); + + return () => clearTimeout(timer); + } + }, [_isOpen]); + const options = useMemo(() => { const o = Array.from(participants?.values() || []) .filter(p => !p.fakeParticipant) @@ -613,8 +685,8 @@ const Chat = ({ } return ( - _isOpen ?
{ */ override componentDidMount() { if (this.props.messages.length > 0) { - this.scrollToElement(false, null); + // Delay scroll to match chat opening animation, then scroll smoothly + setTimeout(() => { + this.scrollToElement(true, null); + }, 300); this._createBottomListObserver(); } }