feat: introduce new categories field for UI Action Buttons (#33066)
Co-authored-by: Tasso Evangelista <2263066+tassoevan@users.noreply.github.com>pull/33479/head
parent
760ae5c01a
commit
debd3ffa22
@ -0,0 +1,7 @@ |
||||
--- |
||||
'@rocket.chat/ui-kit': minor |
||||
'@rocket.chat/i18n': minor |
||||
'@rocket.chat/meteor': minor |
||||
--- |
||||
|
||||
Introduces new property `category` for Rocket.Chat Apps to register UI action buttons. This property is used to group buttons in the UI. |
||||
@ -0,0 +1,89 @@ |
||||
import { useUniqueId } from '@rocket.chat/fuselage-hooks'; |
||||
import { GenericMenu, type GenericMenuItemProps } from '@rocket.chat/ui-client'; |
||||
import { useTranslation } from '@rocket.chat/ui-contexts'; |
||||
import type { MouseEvent, ReactElement } from 'react'; |
||||
import React from 'react'; |
||||
|
||||
import type { MessageActionConditionProps, MessageActionConfig } from '../../../../app/ui-utils/client/lib/MessageAction'; |
||||
|
||||
type MessageActionConfigOption = Omit<MessageActionConfig, 'condition' | 'context' | 'order' | 'action'> & { |
||||
action: (e?: MouseEvent<HTMLElement>) => void; |
||||
}; |
||||
|
||||
type MessageActionSection = { |
||||
id: string; |
||||
title: string; |
||||
items: GenericMenuItemProps[]; |
||||
}; |
||||
|
||||
type MessageActionMenuProps = { |
||||
onChangeMenuVisibility: (visible: boolean) => void; |
||||
options: MessageActionConfigOption[]; |
||||
context: MessageActionConditionProps; |
||||
isMessageEncrypted: boolean; |
||||
}; |
||||
|
||||
const MessageToolbarStarsActionMenu = ({ |
||||
options, |
||||
onChangeMenuVisibility, |
||||
context, |
||||
isMessageEncrypted, |
||||
}: MessageActionMenuProps): ReactElement => { |
||||
const t = useTranslation(); |
||||
const id = useUniqueId(); |
||||
|
||||
const groupOptions = options.reduce((acc, option) => { |
||||
const transformedOption = { |
||||
variant: option.color === 'alert' ? 'danger' : '', |
||||
id: option.id, |
||||
icon: option.icon, |
||||
content: t(option.label), |
||||
onClick: option.action, |
||||
type: option.type, |
||||
...(option.disabled && { disabled: option?.disabled?.(context) }), |
||||
...(option.disabled && |
||||
option?.disabled?.(context) && { tooltip: t('Action_not_available_encrypted_content', { action: t(option.label) }) }), |
||||
}; |
||||
|
||||
const group = option.type || ''; |
||||
let section = acc.find((section: { id: string }) => section.id === group); |
||||
|
||||
if (!section) { |
||||
section = { id: group, title: '', items: [] }; |
||||
acc.push(section); |
||||
} |
||||
|
||||
// Add option to the appropriate section
|
||||
section.items.push(transformedOption); |
||||
|
||||
// Handle the "apps" section if message is encrypted
|
||||
if (group === 'apps' && isMessageEncrypted) { |
||||
section.items = [ |
||||
{ |
||||
content: t('Unavailable'), |
||||
id, |
||||
disabled: true, |
||||
gap: false, |
||||
tooltip: t('Action_not_available_encrypted_content', { action: t('Apps') }), |
||||
}, |
||||
]; |
||||
} |
||||
|
||||
return acc; |
||||
}, [] as MessageActionSection[]); |
||||
|
||||
return ( |
||||
<GenericMenu |
||||
onOpenChange={onChangeMenuVisibility} |
||||
detached |
||||
icon='stars' |
||||
title={t('AI_Actions')} |
||||
data-qa-id='menu' |
||||
data-qa-type='message-action-menu' |
||||
sections={groupOptions} |
||||
placement='bottom-end' |
||||
/> |
||||
); |
||||
}; |
||||
|
||||
export default MessageToolbarStarsActionMenu; |
||||
@ -0,0 +1,24 @@ |
||||
import { MessageActionContext } from '@rocket.chat/apps-engine/definition/ui'; |
||||
import type { IUIActionButton } from '@rocket.chat/apps-engine/definition/ui'; |
||||
import { useCallback } from 'react'; |
||||
|
||||
const DEFAULT_CATEGORY = 'default'; |
||||
|
||||
export const useFilterActionsByContextAndCategory = (context: string | undefined, category = 'default') => { |
||||
return useCallback( |
||||
(action: IUIActionButton) => { |
||||
if (!context) { |
||||
return true; |
||||
} |
||||
|
||||
const actionCategory = action?.category ?? DEFAULT_CATEGORY; |
||||
const messageActionContext = action.when?.messageActionContext || Object.values(MessageActionContext); |
||||
const isContextMatch = messageActionContext.includes(context as MessageActionContext); |
||||
|
||||
const isCategoryMatch = category === DEFAULT_CATEGORY ? actionCategory === DEFAULT_CATEGORY : actionCategory === category; |
||||
|
||||
return isContextMatch && isCategoryMatch; |
||||
}, |
||||
[context, category], |
||||
); |
||||
}; |
||||
Loading…
Reference in new issue