Skip to content

Commit 73b25a7

Browse files
chohongmAhyoungRyu
andauthored
feat: Feedback message (#898)
Fixes: [AC-745](https://sendbird.atlassian.net/browse/AC-745) ### Changelogs ### Feedback Message Now we are supporting Feedback Message feature!<br/> Feedback feature is applied to message with non default `myFeedbackStatus` value.<br/> * Added `MessageFeedbackModal`, `FeedbackIconButton`, and `MobileFeedbackMenu` ### Documentation - https://sendbird.atlassian.net/wiki/spaces/SDK/pages/2184347696/Feedback+for+message ### Figma design - https://www.figma.com/file/SVbXU00FhjztekD8AiVukK/UIKit_Work-file_React?node-id=1819%3A12270&mode=dev ### How to test - https://sendbird.slack.com/archives/C05QD5M7XDH/p1703230285124509 [AC-745]: https://sendbird.atlassian.net/browse/AC-745?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ --------- Co-authored-by: Ahyoung Ryu <[email protected]>
1 parent ee5e60b commit 73b25a7

File tree

33 files changed

+660
-96
lines changed

33 files changed

+660
-96
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@
6767
"react-dom": "^16.8.6 || ^17.0.0 || ^18.0.0"
6868
},
6969
"dependencies": {
70-
"@sendbird/chat": "^4.10.1",
70+
"@sendbird/chat": "^4.10.6",
7171
"@sendbird/uikit-tools": "0.0.1-alpha.43",
7272
"css-vars-ponyfill": "^2.3.2",
7373
"date-fns": "^2.16.1",

rollup.module-exports.mjs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ export default {
7171
'Channel/components/MessageInput': 'src/modules/Channel/components/MessageInput/index.tsx',
7272
'Channel/components/MessageList': 'src/modules/Channel/components/MessageList/index.tsx',
7373
'Channel/components/RemoveMessageModal': 'src/modules/Channel/components/RemoveMessageModal.tsx',
74+
'Channel/components/MessageFeedbackModal': 'src/modules/Channel/components/MessageFeedbackModal/index.tsx',
7475
'Channel/components/TypingIndicator': 'src/modules/Channel/components/TypingIndicator.tsx',
7576
'Channel/components/UnreadCount': 'src/modules/Channel/components/UnreadCount/index.tsx',
7677
'Channel/components/SuggestedMentionList': 'src/modules/Channel/components/SuggestedMentionList/index.tsx',
@@ -205,4 +206,6 @@ export default {
205206
'ui/VoiceMessageInput': 'src/ui/VoiceMessageInput/index.tsx',
206207
'ui/VoiceMessageItemBody': 'src/ui/VoiceMessageItemBody/index.tsx',
207208
'ui/Word': 'src/ui/Word/index.tsx',
209+
'ui/FeedbackIconButton': 'src/ui/FeedbackIconButton/index.tsx',
210+
'ui/MobileFeedbackMenu': 'src/ui/MobileFeedbackMenu/index.tsx',
208211
};

src/hooks/useKeyDown/data.mock.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { FileInfo } from '../../ui/FileViewer/types';
2+
3+
const PROFILE_FILE_INFO: FileInfo = {
4+
name: 'profile image',
5+
url: 'https://static.sendbird.com/sample/profiles/profile_12_512px.png',
6+
type: 'image/png',
7+
};
8+
9+
const EARTH_FILE_INFO: FileInfo = {
10+
name: 'earth image',
11+
url: 'https://sendbird-upload.s3.amazonaws.com/2D7B4CDB-932F-4082-9B09-A1153792DC8D/'
12+
+ 'upload/n/8af7775ca1d34d7681d7e61b56067136.jpg',
13+
type: 'image/jpg',
14+
};
15+
16+
export const FILE_INFO_LIST: FileInfo[] = [
17+
PROFILE_FILE_INFO,
18+
EARTH_FILE_INFO,
19+
PROFILE_FILE_INFO,
20+
EARTH_FILE_INFO,
21+
PROFILE_FILE_INFO,
22+
];

src/ui/FileViewer/__tests__/useKeyDown.spec.tsx renamed to src/hooks/useKeyDown/test/useKeyDown.spec.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,19 @@
11
import { act, render, screen, fireEvent } from '@testing-library/react';
2-
import { useKeyDown } from '../hooks/useKeyDown';
3-
import { FileViewerComponentProps, ViewerTypes } from '../types';
42
import { FILE_INFO_LIST } from '../data.mock';
53
import React, { useRef } from 'react';
4+
import { useKeyDown } from '../useKeyDown';
5+
import { FileViewerComponentProps, MultiFilesViewer, ViewerTypes } from '../../../ui/FileViewer/types';
66

77
const testId = 'dummy';
88

99
function DummyComponent(props: FileViewerComponentProps): React.ReactElement {
1010
const ref = useRef<HTMLDivElement>(null);
11-
const { onKeyDown } = useKeyDown({ props, ref });
11+
const { onClose, onClickLeft, onClickRight } = props as MultiFilesViewer;
12+
const onKeyDown = useKeyDown(ref, {
13+
Escape: (e) => onClose?.(e),
14+
ArrowLeft: () => onClickLeft?.(),
15+
ArrowRight: () => onClickRight?.(),
16+
});
1217
return <div
1318
ref={ref}
1419
onKeyDown={onKeyDown}

src/hooks/useKeyDown/useKeyDown.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { useLayoutEffect } from 'react';
2+
import { usePreservedCallback } from '@sendbird/uikit-tools';
3+
4+
type KeyDownCallbackMap = Record<string, (event: React.KeyboardEvent<HTMLDivElement>) => void>;
5+
6+
export function useKeyDown(
7+
ref: React.RefObject<HTMLDivElement>,
8+
keyDownCallbackMap: KeyDownCallbackMap,
9+
): React.KeyboardEventHandler<HTMLDivElement> {
10+
11+
useLayoutEffect(() => {
12+
ref.current?.focus();
13+
}, [ref.current]);
14+
15+
const onKeyDown: React.KeyboardEventHandler<HTMLDivElement> = usePreservedCallback((event) => {
16+
const callback = keyDownCallbackMap[event.key];
17+
callback?.(event);
18+
event.stopPropagation();
19+
});
20+
21+
return onKeyDown;
22+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
@import '../../../../styles/variables';
2+
3+
.sendbird-message-feedback-modal-content__mobile {
4+
@include mobile() {
5+
max-width: 280px;
6+
padding: 16px;
7+
}
8+
}
9+
10+
.sendbird-message-feedback-modal-footer__root {
11+
display: flex;
12+
justify-content: space-between;
13+
align-items: center;
14+
}
15+
16+
.sendbird-message-feedback-modal-footer__right-content {
17+
display: flex;
18+
gap: 8px;
19+
}
20+
21+
.sendbird-message-feedback-modal-body__root {
22+
margin: 21px 0;
23+
}
24+
25+
.sendbird-message-feedback-modal-header {
26+
@include mobile() {
27+
font-size: 18px;
28+
line-height: 1.33;
29+
}
30+
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import React, { ReactElement, useContext, useRef } from 'react';
2+
import { LocalizationContext } from '../../../../lib/LocalizationContext';
3+
import Modal from '../../../../ui/Modal';
4+
import Button, { ButtonTypes } from '../../../../ui/Button';
5+
import Input from '../../../../ui/Input';
6+
import Label, { LabelColors, LabelTypography } from '../../../../ui/Label';
7+
import './index.scss';
8+
import { useMediaQueryContext } from '../../../../lib/MediaQueryContext';
9+
import { CoreMessageType } from '../../../../utils';
10+
import { FeedbackRating } from '@sendbird/chat/message';
11+
import { useKeyDown } from '../../../../hooks/useKeyDown/useKeyDown';
12+
13+
export interface MessageFeedbackModalProps {
14+
selectedFeedback: FeedbackRating;
15+
message: CoreMessageType;
16+
onCancel?: () => void;
17+
onSubmit?: (comment: string) => void;
18+
onRemove?: () => void;
19+
}
20+
21+
export default function MessageFeedbackModal(props: MessageFeedbackModalProps): ReactElement {
22+
const {
23+
selectedFeedback,
24+
message,
25+
onCancel,
26+
onSubmit,
27+
onRemove,
28+
} = props;
29+
30+
const { stringSet } = useContext(LocalizationContext);
31+
const { isMobile } = useMediaQueryContext();
32+
33+
const modalRef = useRef(null);
34+
const inputRef = useRef(null);
35+
const onKeyDown = useKeyDown(modalRef, {
36+
Enter: () => onSubmit?.(inputRef.current.value ?? ''),
37+
Escape: () => onCancel?.(),
38+
});
39+
40+
return (
41+
<div onKeyDown={onKeyDown}>
42+
<Modal
43+
contentClassName='sendbird-message-feedback-modal-content__mobile'
44+
type={ButtonTypes.PRIMARY}
45+
onCancel={onCancel}
46+
onSubmit={() => {
47+
onSubmit?.(inputRef.current.value ?? '');
48+
}}
49+
submitText={stringSet.BUTTON__SUBMIT}
50+
renderHeader={() => (
51+
<div className='sendbird-modal__header'>
52+
<Label
53+
type={LabelTypography.H_1}
54+
color={LabelColors.ONBACKGROUND_1}
55+
className='sendbird-message-feedback-modal-header'
56+
>
57+
{stringSet.FEEDBACK_MODAL_TITLE}
58+
</Label>
59+
</div>
60+
)}
61+
customFooter={
62+
<div className='sendbird-message-feedback-modal-footer__root'>
63+
{
64+
!isMobile && message?.myFeedback && selectedFeedback === message.myFeedback.rating
65+
? <Button type={ButtonTypes.WARNING} onClick={onRemove}>
66+
<Label type={LabelTypography.BUTTON_3} color={LabelColors.ERROR}>
67+
{stringSet.BUTTON__REMOVE_FEEDBACK}
68+
</Label>
69+
</Button>
70+
: <div/>
71+
}
72+
<div className='sendbird-message-feedback-modal-footer__right-content'>
73+
<Button type={ButtonTypes.SECONDARY} onClick={onCancel}>
74+
<Label type={LabelTypography.BUTTON_3} color={LabelColors.ONBACKGROUND_1}>
75+
{stringSet.BUTTON__CANCEL}
76+
</Label>
77+
</Button>
78+
<Button onClick={() => onSubmit?.(inputRef.current.value ?? '')}>
79+
<Label type={LabelTypography.BUTTON_3} color={LabelColors.ONCONTENT_1}>
80+
{stringSet.BUTTON__SUBMIT}
81+
</Label>
82+
</Button>
83+
</div>
84+
</div>
85+
}
86+
>
87+
<div className='sendbird-message-feedback-modal-body__root'>
88+
<Input
89+
name='sendbird-message-feedback-modal-body__root'
90+
ref={inputRef}
91+
value={(message?.myFeedback && selectedFeedback === message.myFeedback.rating)
92+
? message.myFeedback?.comment
93+
: undefined
94+
}
95+
placeHolder={stringSet.FEEDBACK_CONTENT_PLACEHOLDER}
96+
autoFocus={true}
97+
/>
98+
</div>
99+
</Modal>
100+
</div>
101+
);
102+
}

src/svgs/icon-feedback-dislike.svg

Lines changed: 7 additions & 0 deletions
Loading

src/svgs/icon-feedback-like.svg

Lines changed: 7 additions & 0 deletions
Loading

0 commit comments

Comments
 (0)