UNPKG

stream-chat-react

Version:

React components to create chat conversations or livestream style chat

105 lines (104 loc) 5.11 kB
import React, { createContext, useContext, useEffect, useMemo, useState } from 'react'; import { ThreadProvider } from '../Threads'; import { Icon } from '../Threads/icons'; import { UnreadCountBadge } from '../Threads/UnreadCountBadge'; import { useChatContext } from '../../context'; import { useStateStore } from '../../store'; import clsx from 'clsx'; const ChatViewContext = createContext({ activeChatView: 'channels', setActiveChatView: () => undefined, }); export const ChatView = ({ children }) => { const [activeChatView, setActiveChatView] = useState('channels'); const { theme } = useChatContext(); const value = useMemo(() => ({ activeChatView, setActiveChatView }), [activeChatView]); return (React.createElement(ChatViewContext.Provider, { value: value }, React.createElement("div", { className: clsx('str-chat', theme, 'str-chat__chat-view') }, children))); }; const ChannelsView = ({ children }) => { const { activeChatView } = useContext(ChatViewContext); if (activeChatView !== 'channels') return null; return React.createElement("div", { className: 'str-chat__chat-view__channels' }, children); }; const ThreadsViewContext = createContext({ activeThread: undefined, setActiveThread: () => undefined, }); export const useThreadsViewContext = () => useContext(ThreadsViewContext); const ThreadsView = ({ children }) => { const { activeChatView } = useContext(ChatViewContext); const [activeThread, setActiveThread] = useState(undefined); const value = useMemo(() => ({ activeThread, setActiveThread }), [activeThread]); if (activeChatView !== 'threads') return null; return (React.createElement(ThreadsViewContext.Provider, { value: value }, React.createElement("div", { className: 'str-chat__chat-view__threads' }, children))); }; // thread business logic that's impossible to keep within client but encapsulated for ease of use export const useActiveThread = ({ activeThread }) => { useEffect(() => { if (!activeThread) return; const handleVisibilityChange = () => { if (document.visibilityState === 'visible' && document.hasFocus()) { activeThread.activate(); } if (document.visibilityState === 'hidden' || !document.hasFocus()) { activeThread.deactivate(); } }; handleVisibilityChange(); window.addEventListener('focus', handleVisibilityChange); window.addEventListener('blur', handleVisibilityChange); return () => { activeThread.deactivate(); window.addEventListener('blur', handleVisibilityChange); window.removeEventListener('focus', handleVisibilityChange); }; }, [activeThread]); }; // ThreadList under View.Threads context, will access setting function and on item click will set activeThread // which can be accessed for the ease of use by ThreadAdapter which forwards it to required ThreadProvider // ThreadList can easily live without this context and click handler can be overriden, ThreadAdapter is then no longer needed /** * // this setup still works * const MyCustomComponent = () => { * const [activeThread, setActiveThread] = useState(); * * return <> * // simplified * <ThreadList onItemPointerDown={setActiveThread} /> * <ThreadProvider thread={activeThread}> * <Thread /> * </ThreadProvider> * </> * } * */ const ThreadAdapter = ({ children }) => { const { activeThread } = useThreadsViewContext(); useActiveThread({ activeThread }); return React.createElement(ThreadProvider, { thread: activeThread }, children); }; const selector = ({ unreadThreadCount }) => ({ unreadThreadCount, }); const ChatViewSelector = () => { const { client } = useChatContext(); const { unreadThreadCount } = useStateStore(client.threads.state, selector); const { activeChatView, setActiveChatView } = useContext(ChatViewContext); return (React.createElement("div", { className: 'str-chat__chat-view__selector' }, React.createElement("button", { "aria-selected": activeChatView === 'channels', className: 'str-chat__chat-view__selector-button', onPointerDown: () => setActiveChatView('channels'), role: 'tab' }, React.createElement(Icon.MessageBubbleEmpty, null), React.createElement("div", { className: 'str-chat__chat-view__selector-button-text' }, "Channels")), React.createElement("button", { "aria-selected": activeChatView === 'threads', className: 'str-chat__chat-view__selector-button', onPointerDown: () => setActiveChatView('threads'), role: 'tab' }, React.createElement(UnreadCountBadge, { count: unreadThreadCount, position: 'top-right' }, React.createElement(Icon.MessageBubble, null)), React.createElement("div", { className: 'str-chat__chat-view__selector-button-text' }, "Threads")))); }; ChatView.Channels = ChannelsView; ChatView.Threads = ThreadsView; ChatView.ThreadAdapter = ThreadAdapter; ChatView.Selector = ChatViewSelector;