Skip to content

Commit

Permalink
feature/thread-header-component (#642)
Browse files Browse the repository at this point in the history
* add thread header component and prop

* add test

* add changelog

Co-authored-by: Dan Carbonell <dan@getstream.io>
Co-authored-by: Amin Mahboubi <amin@getstream.io>
  • Loading branch information
3 people authored Dec 15, 2020
1 parent 46b7630 commit d2e92b9
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 26 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## [3.4.1](https://github.com/GetStream/stream-chat-react/releases/tag/v3.4.1) 2020-12-15

## Feature

- Adds custom UI component prop `ThreadHeader` to `Thread` to override the default header. [#642](https://github.com/GetStream/stream-chat-react/pull/642)

## [3.4.0](https://github.com/GetStream/stream-chat-react/releases/tag/v3.4.0) 2020-12-14

## Feature
Expand Down
68 changes: 43 additions & 25 deletions src/components/Thread/Thread.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ class Thread extends PureComponent {
).isRequired),
/** **Available from [channel context](https://getstream.github.io/stream-chat-react/#channel)** */
Message: /** @type {PropTypes.Validator<React.ComponentType<import('types').MessageUIComponentProps>>} */ (PropTypes.elementType),
/** **UI component used to override the default header of the thread** */
ThreadHeader: /** @type {PropTypes.Validator<React.ComponentType<import('types').ThreadHeaderProps>>} */ (PropTypes.elementType),
/**
* **Available from [channel context](https://getstream.github.io/stream-chat-react/#channel)**
* The thread (the parent [message object](https://getstream.io/chat/docs/#message_format)) */
Expand Down Expand Up @@ -106,6 +108,40 @@ class Thread extends PureComponent {
}
}

/**
* @type { React.FC<import('types').ThreadHeaderProps> }
*/
const DefaultThreadHeader = ({ closeThread, t, thread }) => {
const getReplyCount = () => {
if (!thread?.reply_count || !t) return '';
if (thread.reply_count === 1) return t('1 reply');
return t('{{ replyCount }} replies', {
replyCount: thread.reply_count,
});
};

return (
<div className="str-chat__thread-header">
<div className="str-chat__thread-header-details">
<strong>{t && t('Thread')}</strong>
<small>{getReplyCount()}</small>
</div>
<button
onClick={(e) => closeThread && closeThread(e)}
className="str-chat__square-button"
data-testid="close-button"
>
<svg width="10" height="10" xmlns="http://www.w3.org/2000/svg">
<path
d="M9.916 1.027L8.973.084 5 4.058 1.027.084l-.943.943L4.058 5 .084 8.973l.943.943L5 5.942l3.973 3.974.943-.943L5.942 5z"
fillRule="evenodd"
/>
</svg>
</button>
</div>
);
};

/** @extends {PureComponent<Props, any>} */
class ThreadInner extends React.PureComponent {
static propTypes = {
Expand Down Expand Up @@ -178,7 +214,12 @@ class ThreadInner extends React.PureComponent {
}

render() {
const { t, closeThread, thread } = this.props;
const {
t,
closeThread,
thread,
ThreadHeader = DefaultThreadHeader,
} = this.props;

if (!thread) {
return null;
Expand All @@ -191,30 +232,7 @@ class ThreadInner extends React.PureComponent {
this.props.fullWidth ? 'str-chat__thread--full' : ''
}`}
>
<div className="str-chat__thread-header">
<div className="str-chat__thread-header-details">
<strong>{t && t('Thread')}</strong>
<small>
{' '}
{t &&
t('{{ replyCount }} replies', {
replyCount: thread.reply_count,
})}
</small>
</div>
<button
onClick={(e) => closeThread && closeThread(e)}
className="str-chat__square-button"
data-testid="close-button"
>
<svg width="10" height="10" xmlns="http://www.w3.org/2000/svg">
<path
d="M9.916 1.027L8.973.084 5 4.058 1.027.084l-.943.943L4.058 5 .084 8.973l.943.943L5 5.942l3.973 3.974.943-.943L5.942 5z"
fillRule="evenodd"
/>
</svg>
</button>
</div>
<ThreadHeader closeThread={closeThread} t={t} thread={thread} />
<div className="str-chat__thread-list" ref={this.messageList}>
<Message
// @ts-ignore
Expand Down
24 changes: 23 additions & 1 deletion src/components/Thread/__tests__/Thread.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';
import renderer from 'react-test-renderer';
import { render, fireEvent } from '@testing-library/react';
import { render, fireEvent, waitFor } from '@testing-library/react';
import '@testing-library/jest-dom';
import {
generateUser,
Expand Down Expand Up @@ -132,6 +132,28 @@ describe('Thread', () => {
);
});

it('should render a custom ThreadHeader if it is passed as a prop', async () => {
const CustomThreadHeader = jest.fn(() => (
<div data-testid="custom-thread-header" />
));

const { getByTestId } = renderComponent({
ThreadHeader: CustomThreadHeader,
});

await waitFor(() => {
expect(getByTestId('custom-thread-header')).toBeInTheDocument();
expect(CustomThreadHeader).toHaveBeenCalledWith(
expect.objectContaining({
closeThread: channelContextMock.closeThread,
t: i18nMock,
thread: threadStart,
}),
{},
);
});
});

it('should call the closeThread callback if the button is pressed', () => {
const { getByTestId } = renderComponent();

Expand Down
7 changes: 7 additions & 0 deletions types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -934,6 +934,13 @@ export interface ThreadProps {
additionalMessageListProps?: object;
additionalMessageInputProps?: object;
MessageInput?: React.ElementType<MessageInputProps>;
ThreadHeader?: React.ElementType<ThreadHeaderProps>;
}

export interface ThreadHeaderProps {
closeThread?(event: React.SyntheticEvent): void;
t?: i18next.TFunction;
thread?: ReturnType<StreamChatChannelState['messageToImmutable']> | null;
}

export interface TypingIndicatorProps {
Expand Down

0 comments on commit d2e92b9

Please sign in to comment.