Skip to content

Commit

Permalink
feat: Thread (#357)
Browse files Browse the repository at this point in the history
Features:
* Provide new module `Thread`. See the specific informations of this module on the [Docs page](https://sendbird.com/docs/uikit)
  * You can use a combined component `Thread`. Import it with
    ```typescript
    import Thread from "@sendbird/uikit-react/Thread"
    ```
  * Also you can use `ThreadProvider` and `useThreadContext` for customization. Import it with
    ```typescript
    import { ThreadProvider, useThreadContext } from "@sendbird/uikit-react/Thread/context"
    ```
  * And the other UI components are provided under the Thread. `ThreadUI`, `ThreadHeader`, `ParentMessageInfo`, `ParentMessageInfoItem`, `ThreadList`, `ThreadListItem`, and `ThreadMessageInput` are it
* Add `ui/ThreadReplies` component
  ```typescript
  interface ThreadRepliesProps {
    className?: string;
    threadInfo: ThreadInfo;
    onClick?: (e: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLElement>) => void;
  }
  ```
* Add channel props `threadReplySelectType`
  * Type of the value should be
  ```typescript
  enum ThreadReplySelectType { PARENT, THREAD }
  ```
  You can see how to use it below
  ```typescript
  import { ThreadReplySelectType } from "@sendbird/uikit-react/Channel/context";

  <Channel
    ...
    threadReplySelectType={ThreadReplySelectType.PARENT}
  />
  ```

Fixes:
* Do not allow operator to unregister itself on the OperatorList of GroupChannel
* Create new group channel when user open 1:1 channel on the UserProfile
* Register the channel creator as an operator in 1:1 channel
  • Loading branch information
HoonBaek authored Nov 23, 2022
1 parent f18cb4d commit 13e74d9
Show file tree
Hide file tree
Showing 123 changed files with 5,549 additions and 277 deletions.
13 changes: 13 additions & 0 deletions exports.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,18 @@ export default {
'MessageSearch/context': 'src/smart-components/MessageSearch/context/MessageSearchProvider.tsx',
'MessageSearch/components/MessageSearchUI': 'src/smart-components/MessageSearch/components/MessageSearchUI/index.tsx',

// Thread
Thread: 'src/smart-components/Thread/index.tsx',
'Thread/context': 'src/smart-components/Thread/context/ThreadProvider.tsx',
'Thread/context/types': 'src/smart-components/Thread/types.tsx',
'Thread/components/ThreadUI': 'src/smart-components/Thread/components/ThreadUI/index.tsx',
'Thread/components/ThreadHeader': 'src/smart-components/Thread/components/ThreadHeader/index.tsx',
'Thread/components/ParentMessageInfo': 'src/smart-components/Thread/components/ParentMessageInfo/index.tsx',
'Thread/components/ParentMessageInfoItem': 'src/smart-components/Thread/components/ParentMessageInfo/ParentMessageInfoItem.tsx',
'Thread/components/ThreadList': 'src/smart-components/Thread/components/ThreadList/index.tsx',
'Thread/components/ThreadListItem': 'src/smart-components/Thread/components/ThreadList/ThreadListItem.tsx',
'Thread/components/ThreadMessageInput': 'src/smart-components/Thread/components/ThreadMessageInput/index.tsx',

// CreateChannel
CreateChannel: 'src/smart-components/CreateChannel/index.tsx',
'CreateChannel/context': 'src/smart-components/CreateChannel/context/CreateChannelProvider.tsx',
Expand Down Expand Up @@ -154,6 +166,7 @@ export default {
'ui/SortByRow': 'src/ui/SortByRow/index.tsx',
'ui/TextButton': 'src/ui/TextButton/index.tsx',
'ui/TextMessageItemBody': 'src/ui/TextMessageItemBody/index.tsx',
'ui/ThreadReplies': 'src/ui/ThreadReplies/index.tsx',
'ui/ThumbnailMessageItemBody': 'src/ui/ThumbnailMessageItemBody/index.tsx',
'ui/Tooltip': 'src/ui/Tooltip/index.tsx',
'ui/TooltipWrapper': 'src/ui/TooltipWrapper/index.tsx',
Expand Down
235 changes: 223 additions & 12 deletions scripts/index_d_ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ declare module "SendbirdUIKitGlobal" {
SessionHandler,
User,
ApplicationUserListQueryParams,
EmojiContainer,
} from '@sendbird/chat';
import type {
GroupChannel,
Expand All @@ -24,6 +25,7 @@ declare module "SendbirdUIKitGlobal" {
} from '@sendbird/chat/groupChannel';
import type {
AdminMessage,
BaseMessage,
FailedMessageHandler,
FileMessage,
FileMessageCreateParams,
Expand Down Expand Up @@ -268,6 +270,9 @@ declare module "SendbirdUIKitGlobal" {
resizingWidth?: number | string,
resizingHeight?: number | string,
};
isTypingIndicatorEnabledOnChannelList?: boolean;
isMessageReceiptStatusEnabledOnChannelList?: boolean;
replyType: ReplyType;
}
export interface SdkStore {
error: boolean;
Expand Down Expand Up @@ -453,6 +458,11 @@ declare module "SendbirdUIKitGlobal" {
messageListParams?: MessageListParams;
};

export enum ThreadReplySelectType {
PARENT = 'PARENT',
THREAD = 'THREAD',
}

export type ChannelContextProps = {
children?: React.ReactElement;
channelUrl: string;
Expand All @@ -468,6 +478,7 @@ declare module "SendbirdUIKitGlobal" {
onSearchClick?(): void;
onBackClick?(): void;
replyType?: ReplyType;
threadReplySelectType?: ThreadReplySelectType;
queries?: ChannelQueries;
renderUserProfile?: (props: RenderUserProfileProps) => React.ReactNode | React.ReactElement;
disableUserProfile?: boolean;
Expand Down Expand Up @@ -868,6 +879,131 @@ declare module "SendbirdUIKitGlobal" {
onCloseClick?: () => void;
}

/**
* Thread
*/
export enum ChannelStateTypes {
NIL = 'NIL',
LOADING = 'LOADING',
INVALID = 'INVALID',
INITIALIZED = 'INITIALIZED',
}
export enum ParentMessageInfoStateTypes {
NIL = 'NIL',
LOADING = 'LOADING',
INVALID = 'INVALID',
INITIALIZED = 'INITIALIZED',
}
export enum ThreadListStateTypes {
NIL = 'NIL',
LOADING = 'LOADING',
INVALID = 'INVALID',
INITIALIZED = 'INITIALIZED',
}

export interface ThreadProps extends ThreadProviderProps, ThreadContextInitialState {
className?: string;
}

export interface ThreadProviderInterface extends ThreadProviderProps, ThreadContextInitialState {
fetchPrevThreads: (callback?: (messages?: Array<BaseMessage>) => void) => void;
fetchNextThreads: (callback?: (messages?: Array<BaseMessage>) => void) => void;
toggleReaction: (message, key, isReacted) => void;
sendMessage: (props: {
message: UserMessage,
quoteMessage?: UserMessage | FileMessage,
mentionTemplate?: string,
mentionedUsers?: Array<User>,
}) => void;
sendFileMessage: (file: File, quoteMessage: UserMessage | FileMessage) => void;
resendMessage: (failedMessage: UserMessage | FileMessage) => void;
updateMessage: (props, callback?: () => void) => void;
deleteMessage: (message: UserMessage | FileMessage) => Promise<UserMessage | FileMessage>;
nicknamesMap: Map<string, string>;
}

export type ThreadProviderProps = {
children?: React.ReactElement;
channelUrl: string;
message: UserMessage | FileMessage;
onHeaderActionClick?: () => void;
onMoveToParentMessage?: (props: { message: UserMessage | FileMessage, channel: GroupChannel }) => void;
disableUserProfile?: boolean;
renderUserProfile?: (props: { user: User, close: () => void }) => React.ReactElement;
onUserProfileMessage?: (channel: GroupChannel) => void;
}

export interface ThreadContextInitialState {
currentChannel: GroupChannel;
allThreadMessages: Array<BaseMessage>;
parentMessage: UserMessage | FileMessage;
channelStatus: ChannelStateTypes;
parentMessageInfoStatus: ParentMessageInfoStateTypes;
threadListStatus: ThreadListStateTypes;
hasMorePrev: boolean;
hasMoreNext: boolean;
emojiContainer: EmojiContainer;
isMuted: boolean;
isChannelFrozen: boolean;
currentUserId: string;
}

export interface ThreadUIProps {
renderHeader?: () => React.ReactElement;
renderParentMessageInfo?: () => React.ReactElement;
renderMessage?: (props: { message: UserMessage | FileMessage }) => React.ReactElement;
renderMessageInput?: () => React.ReactElement;
renderCustomSeparator?: () => React.ReactElement;
renderParentMessageInfoPlaceholder?: (type: ParentMessageInfoStateTypes) => React.ReactElement;
renderThreadListPlaceHolder?: (type: ThreadListStateTypes) => React.ReactElement;
}

type EventType = React.MouseEvent<HTMLDivElement | HTMLButtonElement> | React.KeyboardEvent<HTMLDivElement>;
export interface ThreadHeaderProps {
className?: string;
channelName: string;
renderActionIcon?: (props: { onActionIconClick: (e: EventType) => void }) => React.ReactElement;
onActionIconClick?: (e: EventType) => void;
onChannelNameClick?: (e: EventType) => void;
}

export interface ParentMessageInfoProps {
className?: string;
}

export interface ParentMessageInfoItemProps {
className?: string;
message: UserMessage | FileMessage;
showFileViewer?: (bool: boolean) => void;
}

export interface ThreadListProps {
className?: string;
allThreadMessages: Array<UserMessage | FileMessage | BaseMessage>;
renderMessage?: (props: {
message: UserMessage | FileMessage,
chainTop: boolean,
chainBottom: boolean,
hasSeparator: boolean,
}) => React.ReactElement;
renderCustomSeparator?: (props: { message: UserMessage | FileMessage }) => React.ReactElement;
scrollRef?: React.RefObject<HTMLDivElement>;
scrollBottom?: number;
}
export interface ThreadListItemProps {
className?: string;
message: UserMessage | FileMessage;
chainTop?: boolean;
chainBottom?: boolean;
hasSeparator?: boolean;
renderCustomSeparator?: (props: { message: UserMessage | FileMessage }) => React.ReactElement;
handleScroll?: () => void;
}

export interface ThreadMessageInputProps {
className?: string;
}

/**
* CreateChannel
*/
Expand Down Expand Up @@ -1122,6 +1258,7 @@ declare module '@sendbird/uikit-react/Channel/context' {
import SendbirdUIKitGlobal from 'SendbirdUIKitGlobal';
export const ChannelProvider: React.FunctionComponent<SendbirdUIKitGlobal.ChannelContextProps>;
export function useChannelContext(): SendbirdUIKitGlobal.ChannelProviderInterface;
export const ThreadReplySelectType: SendbirdUIKitGlobal.ThreadReplySelectType;
}

declare module '@sendbird/uikit-react/Channel/components/ChannelHeader' {
Expand Down Expand Up @@ -1343,6 +1480,64 @@ declare module '@sendbird/uikit-react/MessageSearch/components/MessageSearchUI'
export default MessageSearchUI;
}

/**
* Thread
*/
declare module '@sendbird/uikit-react/Thread' {
import SendbirdUIKitGlobal from 'SendbirdUIKitGlobal';
const Thread: React.FC<SendbirdUIKitGlobal.ThreadProviderProps>;
export default Thread;
}

declare module '@sendbird/uikit-react/Thread/context' {
import SendbirdUIKitGlobal from 'SendbirdUIKitGlobal';
export const useThreadContext: () => SendbirdUIKitGlobal.ThreadProviderInterface;
export const ThreadProvider: React.FC<SendbirdUIKitGlobal.ThreadProviderProps>;
}

declare module '@sendbird/uikit-react/Thread/context/types' {
import SendbirdUIKitGlobal from 'SendbirdUIKitGlobal';
export const ChannelStateTypes: SendbirdUIKitGlobal.ChannelStateTypes;
export const ParentMessageInfoStateTypes: SendbirdUIKitGlobal.ParentMessageInfoStateTypes;
export const ThreadListStateTypes: SendbirdUIKitGlobal.ThreadListStateTypes;
}

declare module '@sendbird/uikit-react/Thread/components/ThreadUI' {
import SendbirdUIKitGlobal from 'SendbirdUIKitGlobal';
const ThreadUI: React.FC<SendbirdUIKitGlobal.ThreadUIProps>;
export default ThreadUI;
}
declare module '@sendbird/uikit-react/Thread/components/ThreadHeader' {
import SendbirdUIKitGlobal from 'SendbirdUIKitGlobal';
const ThreadHeader: React.FC<SendbirdUIKitGlobal.ThreadHeaderProps>;
export default ThreadHeader;
}
declare module '@sendbird/uikit-react/Thread/components/ParentMessageInfo' {
import SendbirdUIKitGlobal from 'SendbirdUIKitGlobal';
const ParentMessageInfo: React.FC<SendbirdUIKitGlobal.ParentMessageInfoProps>;
export default ParentMessageInfo;
}
declare module '@sendbird/uikit-react/Thread/components/ParentMessageInfoItem' {
import SendbirdUIKitGlobal from 'SendbirdUIKitGlobal';
const ParentMessageInfoItem: React.FC<SendbirdUIKitGlobal.ParentMessageInfoItemProps>;
export default ParentMessageInfoItem;
}
declare module '@sendbird/uikit-react/Thread/components/ThreadList' {
import SendbirdUIKitGlobal from 'SendbirdUIKitGlobal';
const ThreadList: React.FC<SendbirdUIKitGlobal.ThreadListProps>;
export default ThreadList;
}
declare module '@sendbird/uikit-react/Thread/components/ThreadListItem' {
import SendbirdUIKitGlobal from 'SendbirdUIKitGlobal';
const ThreadListItem: React.FC<SendbirdUIKitGlobal.ThreadListItemProps>;
export default ThreadListItem;
}
declare module '@sendbird/uikit-react/Thread/components/ThreadMessageInput' {
import SendbirdUIKitGlobal from 'SendbirdUIKitGlobal';
const ThreadMessageInput: React.FC<SendbirdUIKitGlobal.ThreadMessageInputProps>;
export default ThreadMessageInput;
}

/**
* CreateChannel
*/
Expand Down Expand Up @@ -1633,16 +1828,17 @@ declare module '@sendbird/uikit-react/ui/IconButton' {

declare module '@sendbird/uikit-react/ui/ImageRenderer' {
interface ImageRendererProps {
className?: string | Array<string>,
defaultComponent?: () => React.ReactElement,
placeHolder?: () => React.ReactElement,
alt?: string,
width?: number,
height?: number,
fixedSize?: boolean,
circle?: boolean,
onLoad?: () => void,
onError?: () => void,
className?: string | Array<string>;
url: string;
alt?: string;
width?: string | number;
height?: string | number;
circle?: boolean;
fixedSize?: boolean;
placeHolder?: ((props: { style: { [key: string]: string | number } }) => React.ReactElement) | React.ReactElement;
defaultComponent?: (() => React.ReactElement) | React.ReactElement;
onLoad?: () => void;
onError?: () => void;
}
const ImageRenderer: React.FC<ImageRendererProps>;
export default ImageRenderer;
Expand Down Expand Up @@ -1778,20 +1974,23 @@ declare module '@sendbird/uikit-react/ui/MessageItemMenu' {
import type { GroupChannel } from '@sendbird/chat/groupChannel';
import type { FileMessage, UserMessage } from '@sendbird/chat/message';
import type { OpenChannel } from '@sendbird/chat/openChannel';
import type SenbirdUIKitGlobal from 'SendbirdUIKitGlobal';
type ReplyType = "NONE" | "QUOTE_REPLY" | "THREAD";

interface MessageItemMenuProps {
className?: string | Array<string>;
message: UserMessage | FileMessage;
channel: GroupChannel | OpenChannel;
isByMe?: boolean;
disabled?: boolean;
replyType?: SenbirdUIKitGlobal.ReplyType;
replyType?: ReplyType;
disableDeleteMessage?: boolean;
showEdit?: (bool: boolean) => void;
showRemove?: (bool: boolean) => void;
resendMessage?: (message: UserMessage | FileMessage) => void;
setQuoteMessage?: (message: UserMessage | FileMessage) => void;
setSupposedHover?: (bool: boolean) => void;
onReplyInThread?: (props: { message: UserMessage | FileMessage }) => void;
onMoveToParentMessage?: () => void;
}
const MessageItemMenu: React.FC<MessageItemMenuProps>;
export default MessageItemMenu;
Expand Down Expand Up @@ -2087,6 +2286,17 @@ declare module '@sendbird/uikit-react/ui/TextMessageItemBody' {
export default TextMessageItemBody;
}

declare module '@sendbird/uikit-react/ui/ThreadReplies' {
import type { ThreadInfo } from '@sendbird/chat/message';
interface ThreadRepliesProps {
className?: string;
threadInfo: ThreadInfo;
onClick?: (e: React.MouseEvent<HTMLDivElement> | React.KeyboardEvent<HTMLDivElement>) => void;
}
const ThreadReplies: React.FC<ThreadRepliesProps>;
export default ThreadReplies;
}

declare module '@sendbird/uikit-react/ui/ThumbnailMessageItemBody' {
import type { FileMessage } from '@sendbird/chat/message';
interface ThumbnailMessageItemBodyProps {
Expand All @@ -2096,6 +2306,7 @@ declare module '@sendbird/uikit-react/ui/ThumbnailMessageItemBody' {
mouseHover?: boolean;
isReactionEnabled?: boolean;
showFileViewer?: (bool: boolean) => void;
style?: Record<string, any>;
}
const ThumbnailMessageItemBody: React.FC<ThumbnailMessageItemBodyProps>;
export default ThumbnailMessageItemBody;
Expand Down
4 changes: 4 additions & 0 deletions src/lib/Sendbird.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export default function Sendbird(props) {
isMentionEnabled,
isTypingIndicatorEnabledOnChannelList,
isMessageReceiptStatusEnabledOnChannelList,
replyType,
} = props;

const mediaQueryBreakPoint = false;
Expand Down Expand Up @@ -202,6 +203,7 @@ export default function Sendbird(props) {
},
isTypingIndicatorEnabledOnChannelList,
isMessageReceiptStatusEnabledOnChannelList,
replyType,
},
}}
>
Expand Down Expand Up @@ -270,6 +272,7 @@ Sendbird.propTypes = {
}),
isTypingIndicatorEnabledOnChannelList: PropTypes.bool,
isMessageReceiptStatusEnabledOnChannelList: PropTypes.bool,
replyType: PropTypes.oneOf(['NONE', 'QUOTE_REPLY', 'THREAD']),
};

Sendbird.defaultProps = {
Expand All @@ -296,4 +299,5 @@ Sendbird.defaultProps = {
isMentionEnabled: false,
isTypingIndicatorEnabledOnChannelList: false,
isMessageReceiptStatusEnabledOnChannelList: false,
replyType: 'NONE',
};
Loading

0 comments on commit 13e74d9

Please sign in to comment.