From be028ef66eaecc70bdabfff67fea11e74c69f38d Mon Sep 17 00:00:00 2001 From: Anton Arnautov Date: Tue, 29 Oct 2024 12:28:53 +0100 Subject: [PATCH] Introduce /unstable entrypoint, move stuff around --- package.json | 15 + scripts/bundle.mjs | 3 +- src/components/Message/MessageOptions.tsx | 2 +- .../MessageActions_UNSTABLE.tsx | 360 ------------------ src/components/MessageActions/index.ts | 1 - src/context/ComponentContext.tsx | 2 +- .../MessageActions/MessageActions.tsx | 128 +++++++ src/unstable/MessageActions/defaults.tsx | 161 ++++++++ src/unstable/MessageActions/hooks/index.ts | 2 + .../hooks/useBaseMessageActionSetFilter.ts | 80 ++++ .../hooks/useSplitMessageActionSet.ts | 16 + src/unstable/MessageActions/index.ts | 3 + src/unstable/index.ts | 1 + 13 files changed, 410 insertions(+), 364 deletions(-) delete mode 100644 src/components/MessageActions/MessageActions_UNSTABLE.tsx create mode 100644 src/unstable/MessageActions/MessageActions.tsx create mode 100644 src/unstable/MessageActions/defaults.tsx create mode 100644 src/unstable/MessageActions/hooks/index.ts create mode 100644 src/unstable/MessageActions/hooks/useBaseMessageActionSetFilter.ts create mode 100644 src/unstable/MessageActions/hooks/useSplitMessageActionSet.ts create mode 100644 src/unstable/MessageActions/index.ts create mode 100644 src/unstable/index.ts diff --git a/package.json b/package.json index ed4bb3145..0655f6a99 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,18 @@ }, "default": "./dist/plugins/encoders/mp3.js" }, + "./unstable": { + "types": "./dist/unstable/index.d.ts", + "node": { + "require": "./dist/unstable/index.node.cjs", + "import": "./dist/unstable/index.js" + }, + "browser": { + "require": "./dist/unstable/index.browser.cjs", + "import": "./dist/unstable/index.js" + }, + "default": "./dist/unstable/index.js" + }, "./dist/css/*": { "default": "./dist/css/*" }, @@ -70,6 +82,9 @@ ], "mp3-encoder": [ "./dist/plugins/encoders/mp3.d.ts" + ], + "unstable": [ + "./dist/unstable/index.d.ts" ] } }, diff --git a/scripts/bundle.mjs b/scripts/bundle.mjs index 6e4132506..8c2d8e09c 100755 --- a/scripts/bundle.mjs +++ b/scripts/bundle.mjs @@ -10,6 +10,7 @@ const __dirname = dirname(fileURLToPath(import.meta.url)); const sdkEntrypoint = resolve(__dirname, '../src/index.ts'); const emojiEntrypoint = resolve(__dirname, '../src/plugins/Emojis/index.ts'); const mp3EncoderEntrypoint = resolve(__dirname, '../src/plugins/encoders/mp3.ts'); +const unstableEntrypoint = resolve(__dirname, '../src/unstable/index.ts'); const outDir = resolve(__dirname, '../dist'); // Those dependencies are distributed as ES modules, and cannot be externalized @@ -33,7 +34,7 @@ const external = deps.filter((dep) => !bundledDeps.includes(dep)); /** @type esbuild.BuildOptions */ const cjsBundleConfig = { - entryPoints: [sdkEntrypoint, emojiEntrypoint, mp3EncoderEntrypoint], + entryPoints: [sdkEntrypoint, emojiEntrypoint, mp3EncoderEntrypoint, unstableEntrypoint], bundle: true, format: 'cjs', target: 'es2020', diff --git a/src/components/Message/MessageOptions.tsx b/src/components/Message/MessageOptions.tsx index 23352d2ff..a9aa7c81d 100644 --- a/src/components/Message/MessageOptions.tsx +++ b/src/components/Message/MessageOptions.tsx @@ -101,7 +101,7 @@ const UnMemoizedMessageOptions = < }; /** - * @deprecated Use MessageActions_UNSTABLE (`MessageActions` component) instead. + * @deprecated Consider moving to `MessageActions` from `stream-chat-react/unstable` instead. */ export const MessageOptions = React.memo( UnMemoizedMessageOptions, diff --git a/src/components/MessageActions/MessageActions_UNSTABLE.tsx b/src/components/MessageActions/MessageActions_UNSTABLE.tsx deleted file mode 100644 index 373123e33..000000000 --- a/src/components/MessageActions/MessageActions_UNSTABLE.tsx +++ /dev/null @@ -1,360 +0,0 @@ -/* eslint-disable sort-keys */ -import clsx from 'clsx'; -import React, { ComponentPropsWithoutRef, PropsWithChildren, useMemo, useState } from 'react'; - -import { ReactionSelectorWithButton } from '../Reactions/ReactionSelectorWithButton'; -import { - useChannelActionContext, - useChatContext, - useMessageContext, - useTranslationContext, -} from '../../context'; -import { ActionsIcon, ReactionIcon as DefaultReactionIcon, ThreadIcon } from '../Message/icons'; -import { ACTIONS_NOT_WORKING_IN_THREAD, isUserMuted } from '../Message/utils'; -import { useUserRole } from '../Message/hooks'; -import { DialogAnchor, useDialog, useDialogIsOpen } from '../Dialog'; -import { MessageActionsWrapper } from './MessageActions'; - -export type MessageAction = { - Component: React.ComponentType; - placement: 'quick' | 'dropdown'; - type: - | 'reply' - | 'react' - | 'block' - | 'delete' - | 'edit' - | 'mute' - | 'flag' - | 'pin' - | 'quote' - | 'markAsUnread' - // eslint-disable-next-line @typescript-eslint/ban-types - | (string & {}); -}; - -const DefaultMessageActionComponents = { - dropdown: { - Quote() { - const { setQuotedMessage } = useChannelActionContext(); - const { message } = useMessageContext(); - const { t } = useTranslationContext(); - - const handleQuote = () => { - setQuotedMessage(message); - - const elements = message.parent_id - ? document.querySelectorAll('.str-chat__thread .str-chat__textarea__textarea') - : document.getElementsByClassName('str-chat__textarea__textarea'); - const textarea = elements.item(0); - - if (textarea instanceof HTMLTextAreaElement) { - textarea.focus(); - } - }; - - return ( - - {t('Quote')} - - ); - }, - Pin() { - const { handlePin, message } = useMessageContext(); - const { t } = useTranslationContext(); - - return ( - - {!message.pinned ? t('Pin') : t('Unpin')} - - ); - }, - MarkAsUnread() { - const { handleMarkUnread } = useMessageContext(); - const { t } = useTranslationContext(); - - return ( - - {t('Mark as unread')} - - ); - }, - Flag() { - const { handleFlag } = useMessageContext(); - const { t } = useTranslationContext(); - - return ( - - {t('Flag')} - - ); - }, - Mute() { - const { handleMute, message } = useMessageContext(); - const { mutes } = useChatContext(); - const { t } = useTranslationContext(); - - return ( - - {isUserMuted(message, mutes) ? t('Unmute') : t('Mute')} - - ); - }, - Edit() { - const { handleEdit } = useMessageContext(); - const { t } = useTranslationContext(); - - return ( - - {t('Edit Message')} - - ); - }, - Delete() { - const { handleDelete } = useMessageContext(); - const { t } = useTranslationContext(); - - return ( - - {t('Delete')} - - ); - }, - }, - quick: { - React() { - return ; - }, - Reply() { - const { handleOpenThread } = useMessageContext(); - const { t } = useTranslationContext(); - - return ( - - ); - }, - }, -}; - -export const defaultMessageActionSet: MessageAction[] = [ - { Component: DefaultMessageActionComponents.quick.Reply, placement: 'quick', type: 'reply' }, - { Component: DefaultMessageActionComponents.quick.React, placement: 'quick', type: 'react' }, - // { placement: 'dropdown', type: 'block' }, - { - Component: DefaultMessageActionComponents.dropdown.Delete, - placement: 'dropdown', - type: 'delete', - }, - { Component: DefaultMessageActionComponents.dropdown.Edit, placement: 'dropdown', type: 'edit' }, - { Component: DefaultMessageActionComponents.dropdown.Mute, placement: 'dropdown', type: 'mute' }, - { Component: DefaultMessageActionComponents.dropdown.Flag, placement: 'dropdown', type: 'flag' }, - { Component: DefaultMessageActionComponents.dropdown.Pin, placement: 'dropdown', type: 'pin' }, - { - Component: DefaultMessageActionComponents.dropdown.Quote, - placement: 'dropdown', - type: 'quote', - }, - { - Component: DefaultMessageActionComponents.dropdown.MarkAsUnread, - placement: 'dropdown', - type: 'markAsUnread', - }, -] as const; - -export const DefaultDropdownActionButton = ({ - 'aria-selected': ariaSelected = 'false', - children, - className = 'str-chat__message-actions-list-item-button', - role = 'option', - ...rest -}: ComponentPropsWithoutRef<'button'>) => ( - -); - -/** - * Base filter hook which covers actions of type `delete`, `edit`, - * `flag`, `markAsUnread`, `mute`, `quote`, `react` and `reply`, whether - * the rendered message is a reply (replies are limited to certain actions) and - * whether the message has appropriate type and status. - */ -export const useBaseMessageActionSetFilter = ( - messageActionSet: MessageAction[], - disable = false, -) => { - const { initialMessage: isInitialMessage, message } = useMessageContext(); - const { - canDelete, - canEdit, - canFlag, - canMarkUnread, - canMute, - canQuote, - canReact, - canReply, - } = useUserRole(message); - const isMessageThreadReply = typeof message.parent_id === 'string'; - - return useMemo(() => { - if (disable) return messageActionSet; - - // filter out all actions if any of these are true - if ( - isInitialMessage || // not sure whether this thing even works anymore - !message.type || - message.type === 'error' || - message.type === 'system' || - message.type === 'ephemeral' || - message.status === 'failed' || - message.status === 'sending' - ) - return []; - - return messageActionSet.filter(({ type }: MessageAction) => { - // filter out actions with types that do not work in thread - if (ACTIONS_NOT_WORKING_IN_THREAD.includes(type) && isMessageThreadReply) return false; - - if ( - (type === 'delete' && !canDelete) || - (type === 'edit' && !canEdit) || - (type === 'flag' && !canFlag) || - (type === 'markAsUnread' && !canMarkUnread) || - (type === 'mute' && !canMute) || - (type === 'quote' && !canQuote) || - (type === 'react' && !canReact) || - (type === 'reply' && !canReply) - ) - return false; - - return true; - }); - }, [ - canDelete, - canEdit, - canFlag, - canMarkUnread, - canMute, - canQuote, - canReact, - canReply, - isInitialMessage, - isMessageThreadReply, - message.status, - message.type, - disable, - messageActionSet, - ]); -}; - -export const useSplitMessageActionSet = (messageActionSet: MessageAction[]) => - useMemo(() => { - const quickActionSet: MessageAction[] = []; - const dropdownActionSet: MessageAction[] = []; - - for (const action of messageActionSet) { - if (action.placement === 'quick') quickActionSet.push(action); - if (action.placement === 'dropdown') dropdownActionSet.push(action); - } - - return { quickActionSet, dropdownActionSet } as const; - }, [messageActionSet]); - -// TODO: allow passing down customWrapperClass -export const MessageActions = ({ - disableBaseMessageActionSetFilter = false, - messageActionSet = defaultMessageActionSet, -}: { - disableBaseMessageActionSetFilter?: boolean; - messageActionSet?: MessageAction[]; -}) => { - const { theme } = useChatContext(); - const { isMyMessage, message } = useMessageContext(); - const { t } = useTranslationContext(); - const [actionsBoxButtonElement, setActionsBoxButtonElement] = useState( - null, - ); - - const filteredMessageActionSet = useBaseMessageActionSetFilter( - messageActionSet, - disableBaseMessageActionSetFilter, - ); - - const { dropdownActionSet, quickActionSet } = useSplitMessageActionSet(filteredMessageActionSet); - - const dropdownDialogId = `message-actions--${message.id}`; - const reactionSelectorDialogId = `reaction-selector--${message.id}`; - const dialog = useDialog({ id: dropdownDialogId }); - const dropdownDialogIsOpen = useDialogIsOpen(dropdownDialogId); - const reactionSelectorDialogIsOpen = useDialogIsOpen(reactionSelectorDialogId); - - // do not render anything if total action count is zero - if (dropdownActionSet.length + quickActionSet.length === 0) { - return null; - } - - return ( -
- {dropdownActionSet.length > 0 && ( - - - - - - {dropdownActionSet.map(({ Component: DropdownActionComponent, type }) => ( - - ))} - - - - )} - {quickActionSet.map(({ Component: QuickActionComponent, type }) => ( - - ))} -
- ); -}; - -const DropdownBox = ({ children, open }: PropsWithChildren<{ open: boolean }>) => { - const { t } = useTranslationContext(); - return ( -
-
- {children} -
-
- ); -}; diff --git a/src/components/MessageActions/index.ts b/src/components/MessageActions/index.ts index ac8776105..1790160d9 100644 --- a/src/components/MessageActions/index.ts +++ b/src/components/MessageActions/index.ts @@ -1,4 +1,3 @@ export * from './MessageActions'; export * from './MessageActionsBox'; export * from './CustomMessageActionsList'; -export * as MessageActions_UNSTABLE from './MessageActions_UNSTABLE'; diff --git a/src/context/ComponentContext.tsx b/src/context/ComponentContext.tsx index 09d262466..30409d6ab 100644 --- a/src/context/ComponentContext.tsx +++ b/src/context/ComponentContext.tsx @@ -102,7 +102,7 @@ export type ComponentContextValue< LoadingIndicator?: React.ComponentType; /** Custom UI component to display a message in the standard `MessageList`, defaults to and accepts the same props as: [MessageSimple](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Message/MessageSimple.tsx) */ Message?: React.ComponentType>; - /** Custom UI component for message actions popup, accepts no props, all the defaults are set within [MessageActions_UNSTABLE](https://github.com/GetStream/stream-chat-react/blob/master/src/components/MessageActions/MessageActions_UNSTABLE.tsx) (`MessageActions` component) */ + /** Custom UI component for message actions popup, accepts no props, all the defaults are set within [MessageActions (unstable)](https://github.com/GetStream/stream-chat-react/blob/master/src/unstable/MessageActions/MessageActions.tsx) */ MessageActions?: React.ComponentType; /** Custom UI component to display the contents of a bounced message modal. Usually it allows to retry, edit, or delete the message. Defaults to and accepts the same props as: [MessageBouncePrompt](https://github.com/GetStream/stream-chat-react/blob/master/src/components/MessageBounce/MessageBouncePrompt.tsx) */ MessageBouncePrompt?: React.ComponentType; diff --git a/src/unstable/MessageActions/MessageActions.tsx b/src/unstable/MessageActions/MessageActions.tsx new file mode 100644 index 000000000..d16a57bf8 --- /dev/null +++ b/src/unstable/MessageActions/MessageActions.tsx @@ -0,0 +1,128 @@ +/* eslint-disable sort-keys */ +import clsx from 'clsx'; +import React, { PropsWithChildren, useState } from 'react'; + +import { useChatContext, useMessageContext, useTranslationContext } from '../../context'; +import { ActionsIcon } from '../../components/Message/icons'; +import { DialogAnchor, useDialog, useDialogIsOpen } from '../../components/Dialog'; +import { MessageActionsWrapper } from '../../components/MessageActions/MessageActions'; + +import { useBaseMessageActionSetFilter, useSplitMessageActionSet } from './hooks'; +import { defaultMessageActionSet } from './defaults'; + +export type MessageActionSetItem = { + Component: React.ComponentType; + placement: 'quick' | 'dropdown'; + type: + | 'reply' + | 'react' + | 'block' + | 'delete' + | 'edit' + | 'mute' + | 'flag' + | 'pin' + | 'quote' + | 'markAsUnread' + // eslint-disable-next-line @typescript-eslint/ban-types + | (string & {}); +}; + +export type MessageActionsProps = { + disableBaseMessageActionSetFilter?: boolean; + messageActionSet?: MessageActionSetItem[]; +}; + +// TODO: allow passing down customWrapperClass +/** + * A new actions component to replace current `MessageOptions` component. + * Exports from `stream-chat-react/unstable` __MIGHT__ change - use with caution + * and follow release notes in case you notice unexpected behavior. + */ +export const MessageActions = ({ + disableBaseMessageActionSetFilter = false, + messageActionSet = defaultMessageActionSet, +}: MessageActionsProps) => { + const { theme } = useChatContext(); + const { isMyMessage, message } = useMessageContext(); + const { t } = useTranslationContext(); + const [actionsBoxButtonElement, setActionsBoxButtonElement] = useState( + null, + ); + + const filteredMessageActionSet = useBaseMessageActionSetFilter( + messageActionSet, + disableBaseMessageActionSetFilter, + ); + + const { dropdownActionSet, quickActionSet } = useSplitMessageActionSet(filteredMessageActionSet); + + const dropdownDialogId = `message-actions--${message.id}`; + const reactionSelectorDialogId = `reaction-selector--${message.id}`; + const dialog = useDialog({ id: dropdownDialogId }); + const dropdownDialogIsOpen = useDialogIsOpen(dropdownDialogId); + const reactionSelectorDialogIsOpen = useDialogIsOpen(reactionSelectorDialogId); + + // do not render anything if total action count is zero + if (dropdownActionSet.length + quickActionSet.length === 0) { + return null; + } + + return ( +
+ {dropdownActionSet.length > 0 && ( + + + + + + {dropdownActionSet.map(({ Component: DropdownActionComponent, type }) => ( + + ))} + + + + )} + {quickActionSet.map(({ Component: QuickActionComponent, type }) => ( + + ))} +
+ ); +}; + +const DropdownBox = ({ children, open }: PropsWithChildren<{ open: boolean }>) => { + const { t } = useTranslationContext(); + return ( +
+
+ {children} +
+
+ ); +}; diff --git a/src/unstable/MessageActions/defaults.tsx b/src/unstable/MessageActions/defaults.tsx new file mode 100644 index 000000000..1051842d4 --- /dev/null +++ b/src/unstable/MessageActions/defaults.tsx @@ -0,0 +1,161 @@ +/* eslint-disable sort-keys */ +import React, { ComponentPropsWithoutRef } from 'react'; + +import { isUserMuted } from '../../components'; +import { ReactionIcon as DefaultReactionIcon, ThreadIcon } from '../../components/Message/icons'; +import { ReactionSelectorWithButton } from '../../components/Reactions/ReactionSelectorWithButton'; +import { + useChannelActionContext, + useChatContext, + useMessageContext, + useTranslationContext, +} from '../../context'; + +import type { MessageActionSetItem } from './MessageActions'; + +export const DefaultDropdownActionButton = ({ + 'aria-selected': ariaSelected = 'false', + children, + className = 'str-chat__message-actions-list-item-button', + role = 'option', + ...rest +}: ComponentPropsWithoutRef<'button'>) => ( + +); + +const DefaultMessageActionComponents = { + dropdown: { + Quote() { + const { setQuotedMessage } = useChannelActionContext(); + const { message } = useMessageContext(); + const { t } = useTranslationContext(); + + const handleQuote = () => { + setQuotedMessage(message); + + const elements = message.parent_id + ? document.querySelectorAll('.str-chat__thread .str-chat__textarea__textarea') + : document.getElementsByClassName('str-chat__textarea__textarea'); + const textarea = elements.item(0); + + if (textarea instanceof HTMLTextAreaElement) { + textarea.focus(); + } + }; + + return ( + + {t('Quote')} + + ); + }, + Pin() { + const { handlePin, message } = useMessageContext(); + const { t } = useTranslationContext(); + + return ( + + {!message.pinned ? t('Pin') : t('Unpin')} + + ); + }, + MarkAsUnread() { + const { handleMarkUnread } = useMessageContext(); + const { t } = useTranslationContext(); + + return ( + + {t('Mark as unread')} + + ); + }, + Flag() { + const { handleFlag } = useMessageContext(); + const { t } = useTranslationContext(); + + return ( + + {t('Flag')} + + ); + }, + Mute() { + const { handleMute, message } = useMessageContext(); + const { mutes } = useChatContext(); + const { t } = useTranslationContext(); + + return ( + + {isUserMuted(message, mutes) ? t('Unmute') : t('Mute')} + + ); + }, + Edit() { + const { handleEdit } = useMessageContext(); + const { t } = useTranslationContext(); + + return ( + + {t('Edit Message')} + + ); + }, + Delete() { + const { handleDelete } = useMessageContext(); + const { t } = useTranslationContext(); + + return ( + + {t('Delete')} + + ); + }, + }, + quick: { + React() { + return ; + }, + Reply() { + const { handleOpenThread } = useMessageContext(); + const { t } = useTranslationContext(); + + return ( + + ); + }, + }, +}; + +export const defaultMessageActionSet: MessageActionSetItem[] = [ + { Component: DefaultMessageActionComponents.quick.Reply, placement: 'quick', type: 'reply' }, + { Component: DefaultMessageActionComponents.quick.React, placement: 'quick', type: 'react' }, + // { placement: 'dropdown', type: 'block' }, + { + Component: DefaultMessageActionComponents.dropdown.Delete, + placement: 'dropdown', + type: 'delete', + }, + { Component: DefaultMessageActionComponents.dropdown.Edit, placement: 'dropdown', type: 'edit' }, + { Component: DefaultMessageActionComponents.dropdown.Mute, placement: 'dropdown', type: 'mute' }, + { Component: DefaultMessageActionComponents.dropdown.Flag, placement: 'dropdown', type: 'flag' }, + { Component: DefaultMessageActionComponents.dropdown.Pin, placement: 'dropdown', type: 'pin' }, + { + Component: DefaultMessageActionComponents.dropdown.Quote, + placement: 'dropdown', + type: 'quote', + }, + { + Component: DefaultMessageActionComponents.dropdown.MarkAsUnread, + placement: 'dropdown', + type: 'markAsUnread', + }, +] as const; diff --git a/src/unstable/MessageActions/hooks/index.ts b/src/unstable/MessageActions/hooks/index.ts new file mode 100644 index 000000000..d9e76dddd --- /dev/null +++ b/src/unstable/MessageActions/hooks/index.ts @@ -0,0 +1,2 @@ +export * from './useBaseMessageActionSetFilter'; +export * from './useSplitMessageActionSet'; diff --git a/src/unstable/MessageActions/hooks/useBaseMessageActionSetFilter.ts b/src/unstable/MessageActions/hooks/useBaseMessageActionSetFilter.ts new file mode 100644 index 000000000..33ec360c0 --- /dev/null +++ b/src/unstable/MessageActions/hooks/useBaseMessageActionSetFilter.ts @@ -0,0 +1,80 @@ +import { useMemo } from 'react'; + +import { ACTIONS_NOT_WORKING_IN_THREAD, useUserRole } from '../../../components'; +import { useMessageContext } from '../../../context'; + +import type { MessageActionSetItem } from '../MessageActions'; + +/** + * Base filter hook which covers actions of type `delete`, `edit`, + * `flag`, `markAsUnread`, `mute`, `quote`, `react` and `reply`, whether + * the rendered message is a reply (replies are limited to certain actions) and + * whether the message has appropriate type and status. + */ +export const useBaseMessageActionSetFilter = ( + messageActionSet: MessageActionSetItem[], + disable = false, +) => { + const { initialMessage: isInitialMessage, message } = useMessageContext(); + const { + canDelete, + canEdit, + canFlag, + canMarkUnread, + canMute, + canQuote, + canReact, + canReply, + } = useUserRole(message); + const isMessageThreadReply = typeof message.parent_id === 'string'; + + return useMemo(() => { + if (disable) return messageActionSet; + + // filter out all actions if any of these are true + if ( + isInitialMessage || // not sure whether this thing even works anymore + !message.type || + message.type === 'error' || + message.type === 'system' || + message.type === 'ephemeral' || + message.status === 'failed' || + message.status === 'sending' + ) + return []; + + return messageActionSet.filter(({ type }: MessageActionSetItem) => { + // filter out actions with types that do not work in thread + if (ACTIONS_NOT_WORKING_IN_THREAD.includes(type) && isMessageThreadReply) return false; + + if ( + (type === 'delete' && !canDelete) || + (type === 'edit' && !canEdit) || + (type === 'flag' && !canFlag) || + (type === 'markAsUnread' && !canMarkUnread) || + (type === 'mute' && !canMute) || + (type === 'quote' && !canQuote) || + (type === 'react' && !canReact) || + (type === 'reply' && !canReply) + ) + return false; + + return true; + }); + }, [ + canDelete, + canEdit, + canFlag, + canMarkUnread, + canMute, + canQuote, + canReact, + canReply, + isInitialMessage, + isMessageThreadReply, + message.status, + message.type, + disable, + messageActionSet, + ]); +}; diff --git a/src/unstable/MessageActions/hooks/useSplitMessageActionSet.ts b/src/unstable/MessageActions/hooks/useSplitMessageActionSet.ts new file mode 100644 index 000000000..d824c5038 --- /dev/null +++ b/src/unstable/MessageActions/hooks/useSplitMessageActionSet.ts @@ -0,0 +1,16 @@ +import { useMemo } from 'react'; + +import type { MessageActionSetItem } from '../MessageActions'; + +export const useSplitMessageActionSet = (messageActionSet: MessageActionSetItem[]) => + useMemo(() => { + const quickActionSet: MessageActionSetItem[] = []; + const dropdownActionSet: MessageActionSetItem[] = []; + + for (const action of messageActionSet) { + if (action.placement === 'quick') quickActionSet.push(action); + if (action.placement === 'dropdown') dropdownActionSet.push(action); + } + + return { dropdownActionSet, quickActionSet }; + }, [messageActionSet]); diff --git a/src/unstable/MessageActions/index.ts b/src/unstable/MessageActions/index.ts new file mode 100644 index 000000000..1e0b5c79f --- /dev/null +++ b/src/unstable/MessageActions/index.ts @@ -0,0 +1,3 @@ +export * from './MessageActions'; +export * from './defaults'; +export * from './hooks'; diff --git a/src/unstable/index.ts b/src/unstable/index.ts new file mode 100644 index 000000000..23602e51f --- /dev/null +++ b/src/unstable/index.ts @@ -0,0 +1 @@ +export * from './MessageActions';