Skip to content

Commit cf87853

Browse files
authored
feat: apply xxChannel.input.enableDocument dashboard config (#613)
Applied `enable_document` configs to MessageInput component - [x] group_channel.channel.input.enable_document - [x] open_channel.channel.input.enable_document
1 parent 47b6b64 commit cf87853

File tree

11 files changed

+165
-9
lines changed

11 files changed

+165
-9
lines changed

scripts/index_d_ts

+3
Original file line numberDiff line numberDiff line change
@@ -2077,6 +2077,8 @@ declare module '@sendbird/uikit-react/ui/MessageContent' {
20772077
}
20782078

20792079
declare module '@sendbird/uikit-react/ui/MessageInput' {
2080+
import type { GroupChannel } from '@sendbird/chat/groupChannel';
2081+
import type { OpenChannel } from '@sendbird/chat/openChannel';
20802082
import type { User } from '@sendbird/chat';
20812083

20822084
interface MessageInputProps {
@@ -2095,6 +2097,7 @@ declare module '@sendbird/uikit-react/ui/MessageInput' {
20952097
onCancelEdit?: () => void,
20962098
onStartTyping?: () => void,
20972099
channelUrl: string,
2100+
channel?: OpenChannel | GroupChannel,
20982101
mentionSelectedUser?: Array<User>,
20992102
onUserMentioned?: (user: User) => void,
21002103
onMentionStringChange?: (str: string) => void,

src/lib/Sendbird.tsx

+2
Original file line numberDiff line numberDiff line change
@@ -306,10 +306,12 @@ const SendbirdSDK = ({
306306
groupChannel: {
307307
enableOgtag: configs.groupChannel.channel.enableOgtag,
308308
enableTypingIndicator: configs.groupChannel.channel.enableTypingIndicator,
309+
enableDocument: configs.groupChannel.channel.input.enableDocument,
309310
threadReplySelectType: getCaseResolvedThreadReplySelectType(configs.groupChannel.channel.threadReplySelectType).upperCase,
310311
},
311312
openChannel: {
312313
enableOgtag: configs.openChannel.channel.enableOgtag,
314+
enableDocument: configs.openChannel.channel.input.enableDocument,
313315
},
314316
},
315317
}}

src/lib/types.ts

+2
Original file line numberDiff line numberDiff line change
@@ -108,10 +108,12 @@ export interface SendBirdStateConfig {
108108
groupChannel: {
109109
enableOgtag: SBUConfig['groupChannel']['channel']['enableOgtag'];
110110
enableTypingIndicator: SBUConfig['groupChannel']['channel']['enableTypingIndicator'];
111+
enableDocument: SBUConfig['groupChannel']['channel']['input']['enableDocument'];
111112
threadReplySelectType: SBUConfig['groupChannel']['channel']['threadReplySelectType'];
112113
},
113114
openChannel: {
114115
enableOgtag: SBUConfig['openChannel']['channel']['enableOgtag'];
116+
enableDocument: SBUConfig['openChannel']['channel']['input']['enableDocument'];
115117
},
116118
}
117119
export interface SdkStore {

src/modules/Channel/components/Message/index.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,7 @@ const Message = ({
266266
}
267267
<MessageInput
268268
isEdit
269+
channel={currentGroupChannel}
269270
disabled={disabled}
270271
ref={editMessageInputRef}
271272
mentionSelectedUser={selectedUser}

src/modules/OpenChannel/components/OpenChannelInput/index.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ const MessageInputWrapper = (props: MessageInputWrapperProps, ref: React.RefObje
3737
return (
3838
<div className="sendbird-openchannel-footer">
3939
<MessageInput
40+
channel={currentOpenChannel}
4041
ref={ref}
4142
value={value}
4243
disabled={disabled}

src/modules/OpenChannel/components/OpenChannelMessage/index.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ export default function MessagOpenChannelMessageeHoc(props: OpenChannelMessagePr
105105
return (
106106
<MessageInput
107107
isEdit
108+
channel={currentOpenChannel}
108109
disabled={editDisabled}
109110
ref={editMessageInputRef}
110111
message={message as UserMessage}

src/modules/Thread/components/ParentMessageInfo/index.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ export default function ParentMessageInfo({
157157
)
158158
}
159159
<MessageInput
160+
channel={currentChannel}
160161
isEdit
161162
disabled={disabled}
162163
ref={editMessageInputRef}

src/modules/Thread/components/ThreadList/ThreadListItem.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ export default function ThreadListItem({
159159
}
160160
<MessageInput
161161
isEdit
162+
channel={currentChannel}
162163
disabled={disabled}
163164
ref={editMessageInputRef}
164165
mentionSelectedUser={selectedUser}

src/ui/MessageInput/__tests__/MessageInput.spec.js

+90-2
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,100 @@
1-
import React, { useRef } from 'react';
2-
import { render, screen,fireEvent, waitFor } from '@testing-library/react';
1+
import React, { useContext } from 'react';
2+
import { render, renderHook, screen,fireEvent } from '@testing-library/react';
33
import userEvent from '@testing-library/user-event';
44

5+
import useSendbirdStateContext from '../../../hooks/useSendbirdStateContext';
6+
import { useLocalization } from '../../../lib/LocalizationContext';
57
import MessageInput from "../index";
68

79
const noop = () => {};
810

11+
// to mock useSendbirdStateContext
12+
jest.mock('react', () => ({
13+
...jest.requireActual('react'),
14+
useContext: jest.fn(),
15+
}));
16+
jest.mock('../../../lib/LocalizationContext', () => ({
17+
...jest.requireActual('../../../lib/LocalizationContext'),
18+
useLocalization: jest.fn(),
19+
}));
20+
921
describe('ui/MessageInput', () => {
22+
/** Mocking necessary hooks */
23+
beforeEach(() => {
24+
const stateContextValue = {
25+
config: {
26+
groupChannel: {
27+
enableDocument: true,
28+
}
29+
}
30+
};
31+
const localeContextValue = {
32+
stringSet: {},
33+
};
34+
35+
useContext.mockReturnValue(stateContextValue);
36+
useLocalization.mockReturnValue(localeContextValue);
37+
38+
renderHook(() => useSendbirdStateContext());
39+
renderHook(() => useLocalization());
40+
})
41+
42+
describe('Dashboard enableDocument config', () => {
43+
it('should not render file upload icon if groupChannel.enableDocument: false', () => {
44+
const stateContextValue = {
45+
config: {
46+
groupChannel: {
47+
enableDocument: false,
48+
}
49+
}
50+
};
51+
52+
useContext.mockReturnValue(stateContextValue);
53+
renderHook(() => useSendbirdStateContext());
54+
55+
const { container } = render(<MessageInput onSendMessage={noop} value="" channel={{channelType: 'group'}} />);
56+
expect(
57+
container.getElementsByClassName('sendbird-message-input--attach').length
58+
).toBe(0);
59+
});
60+
61+
it('should not render file upload icon if openChannel.enableDocument: false', () => {
62+
const stateContextValue = {
63+
config: {
64+
openChannel: {
65+
enableDocument: false,
66+
}
67+
}
68+
};
69+
70+
useContext.mockReturnValue(stateContextValue);
71+
renderHook(() => useSendbirdStateContext());
72+
73+
const { container } = render(<MessageInput onSendMessage={noop} value="" channel={{channelType: 'open'}} />);
74+
expect(
75+
container.getElementsByClassName('sendbird-message-input--attach').length
76+
).toBe(0);
77+
});
78+
79+
it('should not render file upload icon if openChannel.enableDocument: true', () => {
80+
const stateContextValue = {
81+
config: {
82+
openChannel: {
83+
enableDocument: true,
84+
}
85+
}
86+
};
87+
88+
useContext.mockReturnValue(stateContextValue);
89+
renderHook(() => useSendbirdStateContext());
90+
91+
const { container } = render(<MessageInput onSendMessage={noop} value="" channel={{channelType: 'open'}} />);
92+
expect(
93+
container.getElementsByClassName('sendbird-message-input--attach').length
94+
).toBe(1);
95+
});
96+
})
97+
1098
it('should render upload icon if no text is present', () => {
1199
const { container } = render(<MessageInput onSendMessage={noop} value="" />);
12100
expect(

src/ui/MessageInput/index.jsx

+21-7
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import React, {
44
useState,
55
useEffect,
66
useCallback,
7-
useContext,
87
} from 'react';
98
import PropTypes from 'prop-types';
109

@@ -17,7 +16,9 @@ import Button, { ButtonTypes, ButtonSizes } from '../Button';
1716
import renderMentionLabelToString from '../MentionUserLabel/renderToString';
1817
import Icon, { IconTypes, IconColors } from '../Icon';
1918
import Label, { LabelTypography, LabelColors } from '../Label';
20-
import { LocalizationContext } from '../../lib/LocalizationContext';
19+
import { useLocalization } from '../../lib/LocalizationContext';
20+
import useSendbirdStateContext from '../../hooks/useSendbirdStateContext';
21+
2122
import { nodeListToArray, sanitizeString } from './utils';
2223
import {
2324
arrayEqual,
@@ -27,6 +28,7 @@ import usePaste from './hooks/usePaste';
2728
import { tokenizeMessage } from '../../modules/Message/utils/tokens/tokenize';
2829
import { USER_MENTION_PREFIX } from '../../modules/Message/consts';
2930
import { TOKEN_TYPES } from '../../modules/Message/utils/tokens/types';
31+
import { checkIfFileUploadEnabled } from './messageInputUtils';
3032

3133
const TEXT_FIELD_ID = 'sendbird-message-input-text-field';
3234
const LINE_HEIGHT = 76;
@@ -97,7 +99,14 @@ const MessageInput = React.forwardRef((props, ref) => {
9799
setMentionedUsers,
98100
} = props;
99101
const textFieldId = messageFieldId || TEXT_FIELD_ID;
100-
const { stringSet } = useContext(LocalizationContext);
102+
const { stringSet } = useLocalization();
103+
const { config } = useSendbirdStateContext();
104+
105+
const isFileUploadEnabled = checkIfFileUploadEnabled({
106+
channel,
107+
config,
108+
});
109+
101110
const fileInputRef = useRef(null);
102111
const [isInput, setIsInput] = useState(false);
103112
const [mentionedUserIds, setMentionedUserIds] = useState([]);
@@ -489,7 +498,10 @@ const MessageInput = React.forwardRef((props, ref) => {
489498
{/* file upload icon */}
490499
{
491500
(!isEdit && !isInput) && (
492-
(renderFileUploadIcon?.() || (
501+
(renderFileUploadIcon?.()
502+
// UIKit Dashboard configuration should have lower priority than
503+
// renderFileUploadIcon which is set in code level
504+
|| (isFileUploadEnabled && (
493505
<IconButton
494506
className={`sendbird-message-input--attach ${isVoiceMessageEnabled ? 'is-voice-message-enabled' : ''}`}
495507
height="32px"
@@ -512,7 +524,8 @@ const MessageInput = React.forwardRef((props, ref) => {
512524
onChange={handleUploadFile(onFileUpload)}
513525
/>
514526
</IconButton>
515-
))
527+
)
528+
))
516529
)
517530
}
518531
{/* voice message input trigger */}
@@ -606,7 +619,9 @@ MessageInput.propTypes = {
606619
renderVoiceMessageIcon: PropTypes.func,
607620
renderSendMessageIcon: PropTypes.func,
608621
renderFileUploadIcon: PropTypes.func,
609-
channel: PropTypes.shape({}),
622+
channel: PropTypes.shape({
623+
channelType: PropTypes.string,
624+
}).isRequired,
610625
};
611626

612627
MessageInput.defaultProps = {
@@ -637,7 +652,6 @@ MessageInput.defaultProps = {
637652
renderVoiceMessageIcon: noop,
638653
renderFileUploadIcon: noop,
639654
renderSendMessageIcon: noop,
640-
channel: {},
641655
};
642656

643657
export default MessageInput;
+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/**
2+
* Write new utils here
3+
* Migrate old utils as needed, and delete utils.js
4+
*/
5+
// import { ChannelType } from '@sendbird/chat';
6+
import type { GroupChannel } from '@sendbird/chat/groupChannel';
7+
import type { OpenChannel } from '@sendbird/chat/openChannel';
8+
import { match } from 'ts-pattern';
9+
10+
import type { SendBirdStateConfig } from '../../lib/types';
11+
12+
/**
13+
* FIXME:
14+
* Import this ChannelType enum from @sendbird/chat
15+
* once MessageInput.spec unit tests can be run \wo jest <-> ESM issue
16+
*/
17+
enum ChannelType {
18+
BASE = 'base',
19+
GROUP = 'group',
20+
OPEN = 'open',
21+
}
22+
/**
23+
* FIXME: Simplify this in UIKit@v4
24+
* If customer is using MessageInput inside our modules(ie: Channel, Thread, etc),
25+
* we should use the config from the module.
26+
* If customer is using MessageInput outside our modules(ie: custom UI),
27+
* we expect Channel to be undefined and customer gets control to show/hide file-upload.
28+
* @param {*} channel GroupChannel | OpenChannel
29+
* @param {*} config SendBirdStateConfig
30+
* @returns boolean
31+
*/
32+
export const checkIfFileUploadEnabled = ({ channel, config }: {
33+
channel?: GroupChannel | OpenChannel,
34+
config?: SendBirdStateConfig,
35+
}) => {
36+
const isEnabled = match(channel?.channelType)
37+
.with(ChannelType.GROUP, () => config?.groupChannel?.enableDocument)
38+
.with(ChannelType.OPEN, () => config?.openChannel?.enableDocument)
39+
.otherwise(() => true);
40+
41+
return isEnabled;
42+
};

0 commit comments

Comments
 (0)