Skip to content

Commit 195d3f3

Browse files
Amin MahboubiJaap
andauthored
CRS-279 separate TypingIndicator (#535)
* TypingIndicator pick up data from context * fix TypingIndicator usages in sample apps * remove console.log * style: run linter * TypingIndicator accepts avatar size prop * feat: MessageList support TypingIndicator * fix: thread skip rendering TypingIndicator * feat: VirtualizedMessageList support TypingIndicator * fix: TypingIndicator sample usage * test: update VirtualizedMessageList snapshot * fix watch-styles build-styles script * update styles TypingIndicator Co-authored-by: Jaap <[email protected]>
1 parent ad736a8 commit 195d3f3

File tree

15 files changed

+122
-105
lines changed

15 files changed

+122
-105
lines changed

examples/commerce/src/App.js

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import {
99
MessageInputFlat,
1010
MessageCommerce,
1111
ChannelHeader,
12-
TypingIndicator,
1312
Window,
1413
} from 'stream-chat-react';
1514
import 'stream-chat-react/dist/css/index.css';
@@ -69,12 +68,7 @@ class App extends Component {
6968
>
7069
<Window>
7170
<ChannelHeader />
72-
{this.state.open && (
73-
<MessageList
74-
TypingIndicator={TypingIndicator}
75-
Message={MessageCommerce}
76-
/>
77-
)}
71+
{this.state.open && <MessageList Message={MessageCommerce} />}
7872
<MessageInput
7973
onFocus={!this.state.open ? this.toggleDemo : null}
8074
Input={MessageInputFlat}

examples/messaging/src/App.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import {
1515
ChannelList,
1616
Window,
1717
Thread,
18-
TypingIndicator,
1918
} from 'stream-chat-react';
2019
import 'stream-chat-react/dist/css/index.css';
2120
import './App.css';
@@ -68,7 +67,7 @@ class App extends Component {
6867
<Channel>
6968
<Window>
7069
<ChannelHeader />
71-
<MessageList TypingIndicator={TypingIndicator} />
70+
<MessageList />
7271
<MessageInput Input={MessageInputFlat} focus />
7372
</Window>
7473
<Thread Message={MessageSimple} />

examples/typescript-app/src/App.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import {
1515
ChannelList,
1616
Window,
1717
Thread,
18-
TypingIndicator,
1918
} from 'stream-chat-react';
2019
import 'stream-chat-react/dist/css/index.css';
2120
import './App.css';
@@ -68,7 +67,7 @@ class App extends Component {
6867
<Channel>
6968
<Window>
7069
<ChannelHeader />
71-
<MessageList TypingIndicator={TypingIndicator} />
70+
<MessageList />
7271
<MessageInput Input={MessageInputFlat} focus />
7372
</Window>
7473
<Thread Message={MessageSimple} />

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,8 +157,8 @@
157157
"types": "tsc --strict",
158158
"docs": "styleguidist build",
159159
"docs-server": "styleguidist server",
160-
"watch-styles": "sass --watch src/styles/index.scss dist/styles/styles.css",
161-
"build-styles": "sass src/styles/index.scss dist/styles/styles.css --style compressed",
160+
"watch-styles": "sass --watch src/styles/index.scss dist/css/index.css",
161+
"build-styles": "sass src/styles/index.scss dist/css/index.css --style compressed",
162162
"prettier": "prettier --list-different '**/*.{js,ts,md,json}' .eslintrc.json .prettierrc .babelrc",
163163
"prettier-fix": "prettier --write '**/*.{js,ts,md,json}' .eslintrc.json .prettierrc .babelrc",
164164
"eslint": "eslint '**/*.{js,md}' --max-warnings 0",

src/components/MessageList/MessageList.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { EmptyStateIndicator as DefaultEmptyStateIndicator } from '../EmptyState
1717
import { LoadingIndicator as DefaultLoadingIndicator } from '../Loading';
1818
import { EventComponent } from '../EventComponent';
1919
import { DateSeparator as DefaultDateSeparator } from '../DateSeparator';
20+
import { TypingIndicator as DefaultTypingIndicator } from '../TypingIndicator';
2021

2122
/**
2223
* MessageList - The message list components renders a list of messages. Its a consumer of [Channel Context](https://getstream.github.io/stream-chat-react/#channel)
@@ -228,6 +229,7 @@ class MessageList extends PureComponent {
228229
>
229230
<MessageListInner
230231
EmptyStateIndicator={this.props.EmptyStateIndicator}
232+
TypingIndicator={this.props.TypingIndicator}
231233
MessageSystem={this.props.MessageSystem}
232234
HeaderComponent={this.props.HeaderComponent}
233235
headerPosition={this.props.headerPosition}
@@ -376,6 +378,12 @@ MessageList.propTypes = {
376378
* Defaults to and accepts same props as: [EventComponent](https://github.com/GetStream/stream-chat-react/blob/master/src/components/EventComponent.js)
377379
*/
378380
MessageSystem: PropTypes.elementType,
381+
/**
382+
* Typing indicator UI component to render
383+
*
384+
* Defaults to and accepts same props as: [TypingIndicator](https://github.com/GetStream/stream-chat-react/blob/master/src/components/TypingIndicator/TypingIndicator.js)
385+
* */
386+
TypingIndicator: PropTypes.elementType,
379387
/**
380388
* The UI Indicator to use when MessagerList or ChannelList is empty
381389
* */
@@ -427,6 +435,7 @@ MessageList.defaultProps = {
427435
Attachment,
428436
DateSeparator: DefaultDateSeparator,
429437
LoadingIndicator: DefaultLoadingIndicator,
438+
TypingIndicator: DefaultTypingIndicator,
430439
EmptyStateIndicator: DefaultEmptyStateIndicator,
431440
unsafeHTML: false,
432441
noGroupByUser: false,

src/components/MessageList/MessageListInner.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ const getGroupStyles = (
161161

162162
const MessageListInner = (props) => {
163163
const {
164+
TypingIndicator,
164165
EmptyStateIndicator,
165166
MessageSystem,
166167
DateSeparator,
@@ -289,7 +290,7 @@ const MessageListInner = (props) => {
289290
{...internalInfiniteScrollProps}
290291
>
291292
<ul className="str-chat__ul">{elements}</ul>
292-
293+
{!threadList && <TypingIndicator />}
293294
<div key="bottom" ref={bottomRef} />
294295
</InfiniteScroll>
295296
);

src/components/MessageList/VirtualizedMessageList.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { ChannelContext, TranslationContext } from '../../context';
1414
import { EventComponent } from '../EventComponent';
1515
import { LoadingIndicator as DefaultLoadingIndicator } from '../Loading';
1616
import { EmptyStateIndicator as DefaultEmptyStateIndicator } from '../EmptyStateIndicator';
17+
import { TypingIndicator as DefaultTypingIndicator } from '../TypingIndicator';
1718
import {
1819
FixedHeightMessage,
1920
MessageDeleted as DefaultMessageDeleted,
@@ -37,6 +38,7 @@ const VirtualizedMessageList = ({
3738
Message = FixedHeightMessage,
3839
MessageSystem = EventComponent,
3940
MessageDeleted = DefaultMessageDeleted,
41+
TypingIndicator = DefaultTypingIndicator,
4042
LoadingIndicator = DefaultLoadingIndicator,
4143
EmptyStateIndicator = DefaultEmptyStateIndicator,
4244
}) => {
@@ -122,6 +124,7 @@ const VirtualizedMessageList = ({
122124
<LoadingIndicator size={20} />
123125
</div>
124126
)}
127+
footer={() => TypingIndicator && <TypingIndicator avatarSize={24} />}
125128
startReached={() => {
126129
// mounted.current prevents immediate loadMore on first render
127130
if (mounted.current && hasMore) {

src/components/MessageList/__tests__/VirtualizedMessageList.test.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ jest.mock('../../Message', () => ({
3535
FixedHeightMessage: jest.fn(() => <div>FixedHeightMessage</div>),
3636
}));
3737

38+
jest.mock('../../TypingIndicator', () => ({
39+
TypingIndicator: jest.fn(() => <div>TypingIndicator</div>),
40+
}));
41+
3842
async function createChannel() {
3943
const user1 = generateUser();
4044
const user2 = generateUser();

src/components/MessageList/__tests__/__snapshots__/VirtualizedMessageList.test.js.snap

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,11 @@ exports[`VirtualizedMessageList should render empty list of messages 1`] = `
245245
FixedHeightMessage
246246
</div>
247247
</div>
248+
<footer>
249+
<div>
250+
TypingIndicator
251+
</div>
252+
</footer>
248253
</div>
249254
</div>
250255
<div
Lines changed: 21 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,38 @@
11
// @ts-check
2-
import React from 'react';
3-
import PropTypes from 'prop-types';
2+
import React, { useContext } from 'react';
43

4+
import { ChannelContext } from '../../context';
55
import { Avatar } from '../Avatar';
6+
67
/**
7-
* TypingIndicator lists users currently typing
8+
* TypingIndicator lists users currently typing, it needs to be a child of Channel component
89
* @typedef {import('types').TypingIndicatorProps} Props
910
* @type {React.FC<Props>}
1011
*/
11-
const TypingIndicator = (props) => {
12-
const typing = Object.values(props.typing);
13-
let show;
14-
if (
15-
typing.length === 0 ||
16-
(typing.length === 1 && typing[0].user.id === props.client?.user?.id)
17-
) {
18-
show = false;
19-
} else {
20-
show = true;
21-
}
12+
const TypingIndicator = ({ avatarSize = 32 }) => {
13+
const { typing, client } = useContext(ChannelContext);
14+
15+
if (!typing || !client) return null;
16+
17+
const users = Object.values(typing).filter(
18+
({ user }) => user?.id !== client.user?.id,
19+
);
2220

2321
return (
2422
<div
2523
className={`str-chat__typing-indicator ${
26-
show ? 'str-chat__typing-indicator--typing' : ''
24+
users.length ? 'str-chat__typing-indicator--typing' : ''
2725
}`}
2826
>
2927
<div className="str-chat__typing-indicator__avatars">
30-
{typing
31-
.filter(({ user }) => user.id !== props.client?.user?.id)
32-
.map(({ user }) => (
33-
<Avatar
34-
image={user.image}
35-
size={32}
36-
name={user.name || user.id}
37-
key={user.id}
38-
/>
39-
))}
28+
{users.map(({ user }) => (
29+
<Avatar
30+
image={user?.image}
31+
size={avatarSize}
32+
name={user?.name || user?.id}
33+
key={user?.id}
34+
/>
35+
))}
4036
</div>
4137
<div className="str-chat__typing-indicator__dots">
4238
<span className="str-chat__typing-indicator__dot" />
@@ -47,12 +43,4 @@ const TypingIndicator = (props) => {
4743
);
4844
};
4945

50-
TypingIndicator.propTypes = {
51-
/** @see See [chat context](https://getstream.github.io/stream-chat-react/#chatcontext) doc */
52-
// @ts-ignore
53-
client: PropTypes.object,
54-
/** @see See [channel context](https://getstream.github.io/stream-chat-react/#channelcontext) doc */
55-
typing: PropTypes.object.isRequired,
56-
};
57-
5846
export default React.memo(TypingIndicator);

0 commit comments

Comments
 (0)