diff --git a/CHANGELOG.md b/CHANGELOG.md index 8492bc4485..e0742e77a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,6 +44,7 @@ Breaking changes in this release: ### Added +- Added hero card polymiddleware infrastructure to enable customization of hero card rendering, in PR [#XXXX](https://github.com/microsoft/BotFramework-WebChat/pull/XXXX), by [@compulim](https://github.com/compulim) - (Experimental) Added pre-chat message with starter prompts in Fluent UI, in PR [#5255](https://github.com/microsoft/BotFramework-WebChat/issues/5255) and [#5263](https://github.com/microsoft/BotFramework-WebChat/issues/5263), by [@compulim](https://github.com/compulim) - (Experimental) Added `isPrimary` props to Fluent UI send box. When set, will wire up with `useSendBoxValue` and works with starter prompts in pre-chat message, in PR [#5257](https://github.com/microsoft/BotFramework-WebChat/issues/5257), by [@compulim](https://github.com/compulim) - (Experimental) Expand Fluent theme support to activities and transcript, in PR [#5258](https://github.com/microsoft/BotFramework-WebChat/pull/5258), by [@OEvgeny](https://github.com/OEvgeny) diff --git a/packages/api-middleware/src/PolymiddlewareComposer.tsx b/packages/api-middleware/src/PolymiddlewareComposer.tsx index 79c6179f4f..81bfcd9703 100644 --- a/packages/api-middleware/src/PolymiddlewareComposer.tsx +++ b/packages/api-middleware/src/PolymiddlewareComposer.tsx @@ -17,6 +17,7 @@ import { import { ActivityPolymiddlewareProvider, extractActivityEnhancer } from './activityPolymiddleware'; import { AvatarPolymiddlewareProvider, extractAvatarEnhancer } from './avatarPolymiddleware'; import { ErrorBoxPolymiddlewareProvider, extractErrorBoxEnhancer } from './errorBoxPolymiddleware'; +import { extractHeroCardEnhancer, HeroCardPolymiddlewareProvider } from './heroCardPolymiddleware'; import { Polymiddleware } from './types/Polymiddleware'; const polymiddlewareComposerPropsSchema = pipe( @@ -56,6 +57,13 @@ function PolymiddlewareComposer(props: PolymiddlewareComposerProps) { const errorBoxPolymiddleware = useMemo(() => errorBoxEnhancers.map(enhancer => () => enhancer), [errorBoxEnhancers]); + const heroCardEnhancers = useMemoIterable>( + () => extractHeroCardEnhancer(polymiddleware), + [polymiddleware] + ); + + const heroCardPolymiddleware = useMemo(() => heroCardEnhancers.map(enhancer => () => enhancer), [heroCardEnhancers]); + // Didn't thoroughly think through this part yet, but I am using the first approach for now: // 1. for every type of middleware @@ -66,11 +74,15 @@ function PolymiddlewareComposer(props: PolymiddlewareComposerProps) { // - will need to be rebuilt, as it uses a different `useBuildRenderCallback()` return ( - - - {children} - - + + + + + {children} + + + + ); } diff --git a/packages/api-middleware/src/heroCardPolymiddleware.tsx b/packages/api-middleware/src/heroCardPolymiddleware.tsx new file mode 100644 index 0000000000..511c68a7d7 --- /dev/null +++ b/packages/api-middleware/src/heroCardPolymiddleware.tsx @@ -0,0 +1,65 @@ +import { validateProps } from '@msinternal/botframework-webchat-react-valibot'; +import React, { memo, useMemo } from 'react'; +import { object, pipe, readonly, type InferInput } from 'valibot'; + +import templatePolymiddleware, { + type InferHandler, + type InferHandlerResult, + type InferMiddleware, + type InferProps, + type InferProviderProps, + type InferRenderer, + type InferRequest +} from './private/templatePolymiddleware'; + +const { + createMiddleware: createHeroCardPolymiddleware, + extractEnhancer: extractHeroCardEnhancer, + Provider: HeroCardPolymiddlewareProvider, + Proxy, + reactComponent: heroCardComponent, + useBuildRenderCallback: useBuildRenderHeroCardCallback +} = templatePolymiddleware<{ readonly heroCard: unknown }, { readonly children?: never }>('HeroCard'); + +type HeroCardPolymiddleware = InferMiddleware; +type HeroCardPolymiddlewareHandler = InferHandler; +type HeroCardPolymiddlewareHandlerResult = InferHandlerResult; +type HeroCardPolymiddlewareProps = InferProps; +type HeroCardPolymiddlewareRenderer = InferRenderer; +type HeroCardPolymiddlewareRequest = InferRequest; +type HeroCardPolymiddlewareProviderProps = InferProviderProps; + +const HeroCardPolymiddlewareProxyPropsSchema = pipe( + object({ + heroCard: object({}) + }), + readonly() +); + +type HeroCardPolymiddlewareProxyProps = Readonly>; + +// A friendlier version than the organic . +const HeroCardPolymiddlewareProxy = memo(function HeroCardPolymiddlewareProxy(props: HeroCardPolymiddlewareProxyProps) { + const { heroCard } = validateProps(HeroCardPolymiddlewareProxyPropsSchema, props); + + const request = useMemo(() => ({ heroCard }), [heroCard]); + + return ; +}); + +export { + createHeroCardPolymiddleware, + extractHeroCardEnhancer, + heroCardComponent, + HeroCardPolymiddlewareProvider, + HeroCardPolymiddlewareProxy, + useBuildRenderHeroCardCallback, + type HeroCardPolymiddleware, + type HeroCardPolymiddlewareHandler, + type HeroCardPolymiddlewareHandlerResult, + type HeroCardPolymiddlewareProps, + type HeroCardPolymiddlewareProviderProps, + type HeroCardPolymiddlewareProxyProps, + type HeroCardPolymiddlewareRenderer, + type HeroCardPolymiddlewareRequest +}; diff --git a/packages/api-middleware/src/index.ts b/packages/api-middleware/src/index.ts index 80a766e350..7c2e259ddf 100644 --- a/packages/api-middleware/src/index.ts +++ b/packages/api-middleware/src/index.ts @@ -41,6 +41,21 @@ export { type ErrorBoxPolymiddlewareRequest } from './errorBoxPolymiddleware'; +export { + createHeroCardPolymiddleware, + extractHeroCardEnhancer, + heroCardComponent, + HeroCardPolymiddlewareProxy, + useBuildRenderHeroCardCallback, + type HeroCardPolymiddleware, + type HeroCardPolymiddlewareHandler, + type HeroCardPolymiddlewareHandlerResult, + type HeroCardPolymiddlewareProps, + type HeroCardPolymiddlewareProxyProps, + type HeroCardPolymiddlewareRenderer, + type HeroCardPolymiddlewareRequest +} from './heroCardPolymiddleware'; + // TODO: [P0] Add tests for nesting `polymiddleware`. export { __INTERNAL_DO_NOT_USE__legacyAvatarMiddlewareOriginalRequestSymbol } from './legacy/avatarMiddleware'; export { default as PolymiddlewareComposer } from './PolymiddlewareComposer'; diff --git a/packages/api/src/boot/middleware.ts b/packages/api/src/boot/middleware.ts index cc9f351676..9d7392c79d 100644 --- a/packages/api/src/boot/middleware.ts +++ b/packages/api/src/boot/middleware.ts @@ -27,6 +27,20 @@ export { type AvatarPolymiddlewareRequest } from '@msinternal/botframework-webchat-api-middleware'; +export { + createHeroCardPolymiddleware, + HeroCardPolymiddlewareProxy, + heroCardComponent, + useBuildRenderHeroCardCallback, + type HeroCardPolymiddleware, + type HeroCardPolymiddlewareHandler, + type HeroCardPolymiddlewareHandlerResult, + type HeroCardPolymiddlewareProps, + type HeroCardPolymiddlewareProxyProps, + type HeroCardPolymiddlewareRenderer, + type HeroCardPolymiddlewareRequest +} from '@msinternal/botframework-webchat-api-middleware'; + export { default as AvatarPolymiddlewareProxy, AvatarPolymiddlewareProxyProps diff --git a/packages/bundle/src/adaptiveCards/Attachment/HeroCardAttachment.js b/packages/bundle/src/adaptiveCards/Attachment/HeroCardAttachment.js deleted file mode 100644 index 4ba7b71feb..0000000000 --- a/packages/bundle/src/adaptiveCards/Attachment/HeroCardAttachment.js +++ /dev/null @@ -1,33 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; - -import HeroCardContent from './HeroCardContent'; - -const HeroCardAttachment = ({ attachment: { content } = {}, disabled }) => - !!content && ; - -HeroCardAttachment.defaultProps = { - disabled: undefined -}; - -HeroCardAttachment.propTypes = { - attachment: PropTypes.shape({ - content: PropTypes.shape({ - buttons: PropTypes.any, - images: PropTypes.arrayOf( - PropTypes.shape({ - alt: PropTypes.string, - tap: PropTypes.any, - url: PropTypes.string.isRequired - }) - ), - subtitle: PropTypes.string, - tap: PropTypes.any, - text: PropTypes.string, - title: PropTypes.string - }).isRequired - }).isRequired, - disabled: PropTypes.bool -}; - -export default HeroCardAttachment; diff --git a/packages/bundle/src/adaptiveCards/Attachment/HeroCardAttachment.tsx b/packages/bundle/src/adaptiveCards/Attachment/HeroCardAttachment.tsx new file mode 100644 index 0000000000..b49691db3f --- /dev/null +++ b/packages/bundle/src/adaptiveCards/Attachment/HeroCardAttachment.tsx @@ -0,0 +1,31 @@ +import { validateProps } from '@msinternal/botframework-webchat-react-valibot'; +import React, { memo } from 'react'; +import { boolean, object, optional, pipe, readonly, type InferInput } from 'valibot'; + +import { HeroCardPolymiddlewareProxy } from '../../boot/actual/middleware'; +import { directLineBasicCardSchema } from './private/directLineSchema'; + +const heroCardAttachmentPropsSchema = pipe( + object({ + attachment: pipe( + object({ + content: directLineBasicCardSchema + }), + readonly() + ), + disabled: optional(boolean()) + }), + readonly() +); + +type HeroCardAttachmentProps = InferInput; + +function HeroCardAttachment(props: HeroCardAttachmentProps) { + const { attachment, disabled } = validateProps(heroCardAttachmentPropsSchema, props); + + return attachment.content ? : null; +} + +HeroCardAttachment.displayName = 'HeroCardAttachment'; + +export default memo(HeroCardAttachment); diff --git a/packages/bundle/src/boot/actual/middleware.ts b/packages/bundle/src/boot/actual/middleware.ts index 39662aec03..78941369a2 100644 --- a/packages/bundle/src/boot/actual/middleware.ts +++ b/packages/bundle/src/boot/actual/middleware.ts @@ -44,3 +44,17 @@ export { } from 'botframework-webchat-api/middleware'; export { createAvatarPolymiddlewareFromLegacy } from 'botframework-webchat-api/middleware'; + +export { + createHeroCardPolymiddleware, + heroCardComponent, + HeroCardPolymiddlewareProxy, + useBuildRenderHeroCardCallback, + type HeroCardPolymiddleware, + type HeroCardPolymiddlewareHandler, + type HeroCardPolymiddlewareHandlerResult, + type HeroCardPolymiddlewareProps, + type HeroCardPolymiddlewareProxyProps, + type HeroCardPolymiddlewareRenderer, + type HeroCardPolymiddlewareRequest +} from 'botframework-webchat-api/middleware'; diff --git a/packages/bundle/src/heroCard/createDefaultHeroCardPolymiddleware.tsx b/packages/bundle/src/heroCard/createDefaultHeroCardPolymiddleware.tsx new file mode 100644 index 0000000000..e817652424 --- /dev/null +++ b/packages/bundle/src/heroCard/createDefaultHeroCardPolymiddleware.tsx @@ -0,0 +1,12 @@ +import { createHeroCardPolymiddleware, heroCardComponent } from 'botframework-webchat-api/middleware'; +import React from 'react'; + +import HeroCardContent from '../adaptiveCards/Attachment/HeroCardContent'; + +function createDefaultHeroCardPolymiddleware() { + return createHeroCardPolymiddleware(() => ({ heroCard }) => + heroCardComponent(HeroCardContent, { content: heroCard }) + ); +} + +export default createDefaultHeroCardPolymiddleware; diff --git a/packages/bundle/src/useComposerProps.ts b/packages/bundle/src/useComposerProps.ts index f3679b054b..fd477ebef4 100644 --- a/packages/bundle/src/useComposerProps.ts +++ b/packages/bundle/src/useComposerProps.ts @@ -1,10 +1,11 @@ -import { AttachmentForScreenReaderMiddleware, AttachmentMiddleware } from 'botframework-webchat-api'; +import { AttachmentForScreenReaderMiddleware, AttachmentMiddleware, Polymiddleware } from 'botframework-webchat-api'; import { type HTMLContentTransformMiddleware } from 'botframework-webchat-component'; import { useMemo } from 'react'; import createAdaptiveCardsAttachmentForScreenReaderMiddleware from './adaptiveCards/createAdaptiveCardsAttachmentForScreenReaderMiddleware'; import createAdaptiveCardsAttachmentMiddleware from './adaptiveCards/createAdaptiveCardsAttachmentMiddleware'; import createAdaptiveCardsStyleSet from './adaptiveCards/Styles/createAdaptiveCardsStyleSet'; +import createDefaultHeroCardPolymiddleware from './heroCard/createDefaultHeroCardPolymiddleware'; import createHTMLContentTransformMiddleware from './markdown/createHTMLContentTransformMiddleware'; import defaultRenderMarkdown from './markdown/renderMarkdown'; @@ -31,6 +32,7 @@ export default function useComposerProps({ attachmentMiddleware: AttachmentMiddleware[]; extraStyleSet: any; htmlContentTransformMiddleware: readonly HTMLContentTransformMiddleware[]; + polymiddleware: readonly Polymiddleware[]; renderMarkdown: ( markdown: string, newLineOptions: { markdownRespectCRLF: boolean }, @@ -63,11 +65,17 @@ export default function useComposerProps({ [htmlContentTransformMiddleware] ); + const polymiddleware = useMemo( + () => Object.freeze([createDefaultHeroCardPolymiddleware()]), + [] + ); + return Object.freeze({ attachmentForScreenReaderMiddleware: patchedAttachmentForScreenReaderMiddleware, attachmentMiddleware: patchedAttachmentMiddleware, extraStyleSet, htmlContentTransformMiddleware: patchedHTMLContentTransformMiddleware, + polymiddleware, renderMarkdown: patchedRenderMarkdown }); }