From dfe788c26e09a281d67ac50e27aa2961b85caaab Mon Sep 17 00:00:00 2001 From: William Wong Date: Wed, 7 Aug 2024 11:57:29 -0700 Subject: [PATCH] Add pre-chat message with starter prompts (#5255) * Add pre-chat message with starter prompts * Add pre-chat message * useSuggestedActions: add origin activity * Add maxMessageLength.infinity test * Incorp PR changes * Apply PR suggestions * Flush on CSS vars --- CHANGELOG.md | 12 ++ ...-max-message-length-of-infinity-1-snap.png | Bin 0 -> 8882 bytes ...should-display-pre-chat-message-1-snap.png | Bin 0 -> 32470 bytes ...pre-chat-message-in-wide-format-1-snap.png | Bin 0 -> 44596 bytes .../maxMessageLength.infinity.html | 46 ++++++++ .../fluentTheme/maxMessageLength.infinity.js | 5 + .../fluentTheme/preChatMessageActivity.html | 104 +++++++++++++++++ .../fluentTheme/preChatMessageActivity.js | 5 + .../preChatMessageActivity.wide.html | 106 ++++++++++++++++++ .../preChatMessageActivity.wide.js | 5 + docs/HOOKS.md | 7 +- packages/api/src/hooks/useSuggestedActions.ts | 21 +++- ...tedActions.js => clearSuggestedActions.ts} | 2 +- .../core/src/actions/setSuggestedActions.js | 12 -- .../core/src/actions/setSuggestedActions.ts | 18 +++ packages/core/src/createReducer.ts | 2 + .../core/src/reducers/suggestedActions.js | 2 +- .../suggestedActionsOriginActivity.ts | 33 ++++++ .../src/sagas/queueIncomingActivitySaga.ts | 14 ++- packages/fluent-theme/package-lock.json | 16 ++- packages/fluent-theme/package.json | 3 +- .../PreChatMessageActivity.module.css | 34 ++++++ .../PreChatMessageActivity.tsx | 35 ++++++ .../StarterPromptsCardAction.module.css | 58 ++++++++++ .../StarterPromptsCardAction.tsx | 62 ++++++++++ .../StarterPromptsToolbar.module.css | 18 +++ .../preChatActivity/StarterPromptsToolbar.tsx | 35 ++++++ .../src/components/preChatActivity/index.tsx | 2 + .../isPreChatMessageActivity.ts | 26 +++++ .../private/MonochromeImageMasker.module.css | 5 + .../private/MonochromeImageMasker.tsx | 19 ++++ .../src/components/sendBox/SendBox.tsx | 2 +- .../suggestedActions/SuggestedActions.tsx | 89 ++++++++------- .../src/components/theme/Theme.module.css | 28 ++++- .../src/components/theme/Theme.tsx | 2 +- .../src/private/FluentThemeProvider.tsx | 21 +++- 36 files changed, 771 insertions(+), 78 deletions(-) create mode 100644 __tests__/__image_snapshots__/html/max-message-length-infinity-js-fluent-theme-applied-handles-max-message-length-of-infinity-1-snap.png create mode 100644 __tests__/__image_snapshots__/html/pre-chat-message-activity-js-fluent-theme-applied-should-display-pre-chat-message-1-snap.png create mode 100644 __tests__/__image_snapshots__/html/pre-chat-message-activity-wide-js-fluent-theme-applied-should-display-pre-chat-message-in-wide-format-1-snap.png create mode 100644 __tests__/html/fluentTheme/maxMessageLength.infinity.html create mode 100644 __tests__/html/fluentTheme/maxMessageLength.infinity.js create mode 100644 __tests__/html/fluentTheme/preChatMessageActivity.html create mode 100644 __tests__/html/fluentTheme/preChatMessageActivity.js create mode 100644 __tests__/html/fluentTheme/preChatMessageActivity.wide.html create mode 100644 __tests__/html/fluentTheme/preChatMessageActivity.wide.js rename packages/core/src/actions/{clearSuggestedActions.js => clearSuggestedActions.ts} (94%) delete mode 100644 packages/core/src/actions/setSuggestedActions.js create mode 100644 packages/core/src/actions/setSuggestedActions.ts create mode 100644 packages/core/src/reducers/suggestedActionsOriginActivity.ts create mode 100644 packages/fluent-theme/src/components/preChatActivity/PreChatMessageActivity.module.css create mode 100644 packages/fluent-theme/src/components/preChatActivity/PreChatMessageActivity.tsx create mode 100644 packages/fluent-theme/src/components/preChatActivity/StarterPromptsCardAction.module.css create mode 100644 packages/fluent-theme/src/components/preChatActivity/StarterPromptsCardAction.tsx create mode 100644 packages/fluent-theme/src/components/preChatActivity/StarterPromptsToolbar.module.css create mode 100644 packages/fluent-theme/src/components/preChatActivity/StarterPromptsToolbar.tsx create mode 100644 packages/fluent-theme/src/components/preChatActivity/index.tsx create mode 100644 packages/fluent-theme/src/components/preChatActivity/isPreChatMessageActivity.ts create mode 100644 packages/fluent-theme/src/components/preChatActivity/private/MonochromeImageMasker.module.css create mode 100644 packages/fluent-theme/src/components/preChatActivity/private/MonochromeImageMasker.tsx diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a78ec2bd5..4b9cb64f94 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,18 @@ Notes: web developers are advised to use [`~` (tilde range)](https://github.com/ - `styleOptions.bubbleImageHeight` is being deprecated in favor of `styleOptions.bubbleImageMaxHeight` and `styleOptions.bubbleImageMinHeight`. The option will be removed on or after 2026-07-05 +### Added + +- (Experimental) Added pre-chat message with starter prompts, in PR [#5255](https://github.com/microsoft/BotFramework-WebChat/issues/5255), by [@compulim](https://github.com/compulim) + +### Changed + +- Updated `useSuggestedActions` to return the activity the suggested actions originated from, in PR [#5255](https://github.com/microsoft/BotFramework-WebChat/issues/5255), by [@compulim](https://github.com/compulim) + +### Fixed + +- Fixed [#5256](https://github.com/microsoft/BotFramework-WebChat/issues/5256). `styleOptions.maxMessageLength` should support any JavaScript number value including `Infinity`, by [@compulim](https://github.com/compulim), in PR [#5255](https://github.com/microsoft/BotFramework-WebChat/issues/pull/5255) + ## [4.18.0] - 2024-07-10 ### Added diff --git a/__tests__/__image_snapshots__/html/max-message-length-infinity-js-fluent-theme-applied-handles-max-message-length-of-infinity-1-snap.png b/__tests__/__image_snapshots__/html/max-message-length-infinity-js-fluent-theme-applied-handles-max-message-length-of-infinity-1-snap.png new file mode 100644 index 0000000000000000000000000000000000000000..a1d58ee284dca0f2b2917dd5a2d622048474ee60 GIT binary patch literal 8882 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 WnEeNox5G;)DD*j_v&E+`-}xsfca+2c literal 0 HcmV?d00001 diff --git a/__tests__/__image_snapshots__/html/pre-chat-message-activity-js-fluent-theme-applied-should-display-pre-chat-message-1-snap.png b/__tests__/__image_snapshots__/html/pre-chat-message-activity-js-fluent-theme-applied-should-display-pre-chat-message-1-snap.png new file mode 100644 index 0000000000000000000000000000000000000000..17d4729d57aeb7443b53157fbf6a37bd2d677460 GIT binary patch literal 32470 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 ```js -useSuggestedActions(): [CardAction[], (CardAction[]) => void] +useSuggestedActions(): [CardAction[], (CardAction[]) => void, { activity: WebChatActivity }] ``` -This hook will return an array and a setter function. +This hook will return an array, a setter function, and a property bag. 1. array: a list of suggested actions that should be shown to the user 1. function: a setter function to clear suggested actions. The setter function can only be used to clear suggested actions, and it will accept empty array or falsy value only. +1. `activity`: the activity which the suggested actions are originated from The suggested actions are computed from the last message activity sent from the bot. If the user posts an activity, the suggested actions will be cleared. diff --git a/packages/api/src/hooks/useSuggestedActions.ts b/packages/api/src/hooks/useSuggestedActions.ts index 880adeb185..9402baca17 100644 --- a/packages/api/src/hooks/useSuggestedActions.ts +++ b/packages/api/src/hooks/useSuggestedActions.ts @@ -1,11 +1,23 @@ +import type { DirectLineCardAction, WebChatActivity } from 'botframework-webchat-core'; import { useCallback } from 'react'; -import type { DirectLineCardAction } from 'botframework-webchat-core'; import { useSelector } from './internal/WebChatReduxContext'; import useWebChatAPIContext from './internal/useWebChatAPIContext'; -export default function useSuggestedActions(): [DirectLineCardAction[], (suggestedActions: never[]) => void] { - const value = useSelector(({ suggestedActions }) => suggestedActions); +export default function useSuggestedActions(): [ + DirectLineCardAction[], + (suggestedActions: never[]) => void, + extras: { activity: WebChatActivity } +] { + const [value, activity] = useSelector( + ({ + suggestedActions, + suggestedActionsOriginActivity: { activity } + }: { + suggestedActions: readonly DirectLineCardAction[]; + suggestedActionsOriginActivity: { activity: undefined | WebChatActivity }; + }) => [suggestedActions, activity] + ); const { clearSuggestedActions } = useWebChatAPIContext(); return [ @@ -19,6 +31,7 @@ export default function useSuggestedActions(): [DirectLineCardAction[], (suggest clearSuggestedActions(); }, [clearSuggestedActions] - ) + ), + { activity } ]; } diff --git a/packages/core/src/actions/clearSuggestedActions.js b/packages/core/src/actions/clearSuggestedActions.ts similarity index 94% rename from packages/core/src/actions/clearSuggestedActions.js rename to packages/core/src/actions/clearSuggestedActions.ts index c37dd7a291..47782d11e7 100644 --- a/packages/core/src/actions/clearSuggestedActions.js +++ b/packages/core/src/actions/clearSuggestedActions.ts @@ -1,4 +1,4 @@ -const CLEAR_SUGGESTED_ACTIONS = 'WEB_CHAT/CLEAR_SUGGESTED_ACTIONS'; +const CLEAR_SUGGESTED_ACTIONS = 'WEB_CHAT/CLEAR_SUGGESTED_ACTIONS' as const; export default function clearSuggestedActions() { return { diff --git a/packages/core/src/actions/setSuggestedActions.js b/packages/core/src/actions/setSuggestedActions.js deleted file mode 100644 index a1b854d63c..0000000000 --- a/packages/core/src/actions/setSuggestedActions.js +++ /dev/null @@ -1,12 +0,0 @@ -const EMPTY_ARRAY = []; - -const SET_SUGGESTED_ACTIONS = 'WEB_CHAT/SET_SUGGESTED_ACTIONS'; - -export default function setSuggestedActions(suggestedActions = EMPTY_ARRAY) { - return { - type: SET_SUGGESTED_ACTIONS, - payload: { suggestedActions } - }; -} - -export { SET_SUGGESTED_ACTIONS }; diff --git a/packages/core/src/actions/setSuggestedActions.ts b/packages/core/src/actions/setSuggestedActions.ts new file mode 100644 index 0000000000..e9e55ff194 --- /dev/null +++ b/packages/core/src/actions/setSuggestedActions.ts @@ -0,0 +1,18 @@ +import type { DirectLineCardAction } from '../types/external/DirectLineCardAction'; +import type { WebChatActivity } from '../types/WebChatActivity'; + +const EMPTY_ARRAY: readonly DirectLineCardAction[] = Object.freeze([]); + +const SET_SUGGESTED_ACTIONS = 'WEB_CHAT/SET_SUGGESTED_ACTIONS' as const; + +export default function setSuggestedActions( + suggestedActions: readonly DirectLineCardAction[] = EMPTY_ARRAY, + originActivity: undefined | WebChatActivity = undefined +) { + return { + type: SET_SUGGESTED_ACTIONS, + payload: { originActivity, suggestedActions } + }; +} + +export { SET_SUGGESTED_ACTIONS }; diff --git a/packages/core/src/createReducer.ts b/packages/core/src/createReducer.ts index bca68d263d..3e9c9894b1 100644 --- a/packages/core/src/createReducer.ts +++ b/packages/core/src/createReducer.ts @@ -16,6 +16,7 @@ import sendTimeout from './reducers/sendTimeout'; import sendTypingIndicator from './reducers/sendTypingIndicator'; import shouldSpeakIncomingActivity from './reducers/shouldSpeakIncomingActivity'; import suggestedActions from './reducers/suggestedActions'; +import suggestedActionsOriginActivity from './reducers/suggestedActionsOriginActivity'; import type { GlobalScopePonyfill } from './types/GlobalScopePonyfill'; @@ -36,6 +37,7 @@ export default function createReducer(ponyfill: GlobalScopePonyfill) { sendTypingIndicator, shouldSpeakIncomingActivity, suggestedActions, + suggestedActionsOriginActivity, typing: createTypingReducer(ponyfill) }); } diff --git a/packages/core/src/reducers/suggestedActions.js b/packages/core/src/reducers/suggestedActions.js index 706b308684..93bae8d9c4 100644 --- a/packages/core/src/reducers/suggestedActions.js +++ b/packages/core/src/reducers/suggestedActions.js @@ -1,7 +1,7 @@ import { CLEAR_SUGGESTED_ACTIONS } from '../actions/clearSuggestedActions'; import { SET_SUGGESTED_ACTIONS } from '../actions/setSuggestedActions'; -const DEFAULT_STATE = []; +const DEFAULT_STATE = Object.freeze([]); export default function suggestedActions(state = DEFAULT_STATE, { payload = {}, type }) { switch (type) { diff --git a/packages/core/src/reducers/suggestedActionsOriginActivity.ts b/packages/core/src/reducers/suggestedActionsOriginActivity.ts new file mode 100644 index 0000000000..8b2d06cd10 --- /dev/null +++ b/packages/core/src/reducers/suggestedActionsOriginActivity.ts @@ -0,0 +1,33 @@ +import type clearSuggestedActions from '../actions/clearSuggestedActions'; +import { CLEAR_SUGGESTED_ACTIONS } from '../actions/clearSuggestedActions'; +import type setSuggestedActions from '../actions/setSuggestedActions'; +import { SET_SUGGESTED_ACTIONS } from '../actions/setSuggestedActions'; +import type { WebChatActivity } from '../types/WebChatActivity'; + +type ClearSuggestedActions = ReturnType; +type SetSuggestedActions = ReturnType; +type State = Readonly<{ activity: undefined | WebChatActivity }>; + +const DEFAULT_STATE: State = Object.freeze({ activity: undefined }); + +export default function suggestedActionsOriginActivity( + state = DEFAULT_STATE, + action: ClearSuggestedActions | SetSuggestedActions +): State { + switch (action.type) { + case SET_SUGGESTED_ACTIONS: + state = { activity: action.payload.originActivity }; + + break; + + case CLEAR_SUGGESTED_ACTIONS: + state = DEFAULT_STATE; + + break; + + default: + break; + } + + return state; +} diff --git a/packages/core/src/sagas/queueIncomingActivitySaga.ts b/packages/core/src/sagas/queueIncomingActivitySaga.ts index d4038fc435..88717edc3e 100644 --- a/packages/core/src/sagas/queueIncomingActivitySaga.ts +++ b/packages/core/src/sagas/queueIncomingActivitySaga.ts @@ -1,10 +1,10 @@ import { call, cancelled, fork, put, race, select, take } from 'redux-saga/effects'; -import { QUEUE_INCOMING_ACTIVITY } from '../actions/queueIncomingActivity'; -import activitiesSelector, { ofType as activitiesOfType } from '../selectors/activities'; -import activityFromBot from '../definitions/activityFromBot'; import incomingActivity, { INCOMING_ACTIVITY } from '../actions/incomingActivity'; +import { QUEUE_INCOMING_ACTIVITY } from '../actions/queueIncomingActivity'; import setSuggestedActions from '../actions/setSuggestedActions'; +import activityFromBot from '../definitions/activityFromBot'; +import activitiesSelector, { ofType as activitiesOfType } from '../selectors/activities'; import sleep from '../utils/sleep'; import whileConnected from './effects/whileConnected'; @@ -94,7 +94,13 @@ function* queueIncomingActivity({ userID }: { userID: string }, ponyfill: Global // If suggested actions is not destined to anyone, or is destined to the user, show it. // In other words, if suggested actions is destined to someone else, don't show it. - yield put(setSuggestedActions(to?.length && !to.includes(userID) ? null : actions)); + const suggestedActions = to?.length && !to.includes(userID) ? null : actions; + + if (suggestedActions) { + yield put(setSuggestedActions(suggestedActions, lastMessageActivity)); + } else { + yield put(setSuggestedActions()); + } } } ); diff --git a/packages/fluent-theme/package-lock.json b/packages/fluent-theme/package-lock.json index 230212504a..c7c8ceef3a 100644 --- a/packages/fluent-theme/package-lock.json +++ b/packages/fluent-theme/package-lock.json @@ -12,7 +12,8 @@ "classnames": "2.5.1", "inject-meta-tag": "0.0.1", "math-random": "2.0.1", - "use-ref-from": "0.1.0" + "use-ref-from": "0.1.0", + "valibot": "^0.37.0" }, "devDependencies": { "@tsconfig/strictest": "^2.0.5", @@ -2026,6 +2027,19 @@ "react": ">=16.8.0" } }, + "node_modules/valibot": { + "version": "0.37.0", + "resolved": "https://registry.npmjs.org/valibot/-/valibot-0.37.0.tgz", + "integrity": "sha512-FQz52I8RXgFgOHym3XHYSREbNtkgSjF9prvMFH1nBsRyfL6SfCzoT1GuSDTlbsuPubM7/6Kbw0ZMQb8A+V+VsQ==", + "peerDependencies": { + "typescript": ">=5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/webidl-conversions": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", diff --git a/packages/fluent-theme/package.json b/packages/fluent-theme/package.json index a8e24727a6..d0c58b8272 100644 --- a/packages/fluent-theme/package.json +++ b/packages/fluent-theme/package.json @@ -75,7 +75,8 @@ "classnames": "2.5.1", "inject-meta-tag": "0.0.1", "math-random": "2.0.1", - "use-ref-from": "0.1.0" + "use-ref-from": "0.1.0", + "valibot": "^0.37.0" }, "peerDependencies": { "react": ">= 16.8.6" diff --git a/packages/fluent-theme/src/components/preChatActivity/PreChatMessageActivity.module.css b/packages/fluent-theme/src/components/preChatActivity/PreChatMessageActivity.module.css new file mode 100644 index 0000000000..528617e6a5 --- /dev/null +++ b/packages/fluent-theme/src/components/preChatActivity/PreChatMessageActivity.module.css @@ -0,0 +1,34 @@ +:global(.webchat-fluent) .pre-chat-message-activity { + display: grid; + grid-template-areas: 'body' 'toolbar'; + grid-template-rows: auto auto; + gap: var(--webchat-spacingHorizontalXXXL); + padding: var(--webchat-spacingHorizontalXXXL); +} + +:global(.webchat-fluent) .pre-chat-message-activity__body { + font-family: var(--webchat-fontFamilyBase); + font-size: var(--webchat-fontSizeBase300); + font-weight: var(--webchat-fontWeightRegular); + grid-area: body; + line-height: var(--webchat-lineHeightBase300); + text-align: center; +} + +:global(.webchat-fluent) .pre-chat-message-activity__body h2 { + color: var(--webchat-colorNeutralForeground1); + font-family: inherit; + font-weight: var(--webchat-fontWeightSemibold); + font-size: var(--webchat-fontSizeHero700); + line-height: var(--webchat-lineHeightHero700); + margin: var(--webchat-spacingVerticalL) 0 0; +} + +:global(.webchat-fluent) .pre-chat-message-activity__body img { + border-radius: 4px; + height: 64px; +} + +:global(.webchat-fluent) .pre-chat-message-activity__toolbar { + grid-area: toolbar; +} diff --git a/packages/fluent-theme/src/components/preChatActivity/PreChatMessageActivity.tsx b/packages/fluent-theme/src/components/preChatActivity/PreChatMessageActivity.tsx new file mode 100644 index 0000000000..de0f0a9456 --- /dev/null +++ b/packages/fluent-theme/src/components/preChatActivity/PreChatMessageActivity.tsx @@ -0,0 +1,35 @@ +import { hooks } from 'botframework-webchat-component'; +import { type WebChatActivity } from 'botframework-webchat-core'; +import React, { memo, useMemo } from 'react'; +import { useStyles } from '../../styles/index.js'; +import styles from './PreChatMessageActivity.module.css'; +import StarterPromptsToolbar from './StarterPromptsToolbar.js'; + +type Props = Readonly<{ activity: WebChatActivity & { type: 'message' } }>; + +const { useRenderMarkdownAsHTML } = hooks; + +const PreChatMessageActivity = ({ activity }: Props) => { + const classNames = useStyles(styles); + const renderMarkdownAsHTML = useRenderMarkdownAsHTML(); + + const html = useMemo( + () => (renderMarkdownAsHTML ? { __html: renderMarkdownAsHTML(activity.text || '') } : { __html: '' }), + [activity.text, renderMarkdownAsHTML] + ); + + return ( +
+ {/* eslint-disable-next-line react/no-danger */} +
+ +
+ ); +}; + +PreChatMessageActivity.displayName = 'PreChatMessageActivity'; + +export default memo(PreChatMessageActivity); diff --git a/packages/fluent-theme/src/components/preChatActivity/StarterPromptsCardAction.module.css b/packages/fluent-theme/src/components/preChatActivity/StarterPromptsCardAction.module.css new file mode 100644 index 0000000000..867d038646 --- /dev/null +++ b/packages/fluent-theme/src/components/preChatActivity/StarterPromptsCardAction.module.css @@ -0,0 +1,58 @@ +:global(.webchat-fluent) .pre-chat-message-activity__card-action-box { + appearance: none; + background-color: var(--webchat-colorNeutralBackground1); + border: 0; + border-radius: 16px; + box-shadow: var(--webchat-shadow2); + color: var(--webchat-colorNeutralForeground1); + cursor: pointer; + display: grid; + gap: 8px; + grid-template-areas: 'image title' 'image subtitle'; + grid-template-columns: 20px 1fr; + grid-template-rows: auto 1fr; + overflow: hidden; + padding: 16px 20px; + text-align: left; + user-select: none; +} + +:global(.webchat-fluent) .pre-chat-message-activity__card-action-box:disabled { + background-color: var(--webchat-colorNeutralBackground1Disabled); +} + +:global(.webchat-fluent) .pre-chat-message-activity__card-action-box:hover { + background-color: var(--webchat-colorNeutralGrey94); +} + +:global(.webchat-fluent) .pre-chat-message-activity__card-action-box:active { + background-color: var(--webchat-colorNeutralBackground1Pressed); +} + +:global(.webchat-fluent) .pre-chat-message-activity__card-action-box:focus-visible { + outline: solid 2px var(--webchat-colorStrokeFocus2); + outline-offset: -2px; +} + +:global(.webchat-fluent) .pre-chat-message-activity__card-action-image { + grid-area: image; + height: 20px; + width: 20px; +} + +:global(.webchat-fluent) .pre-chat-message-activity__card-action-subtitle { + font-family: var(--webchat-fontFamilyBase); + font-size: 14px; + font-weight: var(--webchat-fontWeightRegular); + grid-area: subtitle; + line-height: 20px; + pointer-events: none; /* Links in subtitle are not clickable. */ +} + +:global(.webchat-fluent) .pre-chat-message-activity__card-action-title { + font-family: var(--webchat-fontFamilyBase); + font-size: 14px; + font-weight: var(--webchat-fontWeightSemibold); + grid-area: title; + line-height: 20px; +} diff --git a/packages/fluent-theme/src/components/preChatActivity/StarterPromptsCardAction.tsx b/packages/fluent-theme/src/components/preChatActivity/StarterPromptsCardAction.tsx new file mode 100644 index 0000000000..11cc78d840 --- /dev/null +++ b/packages/fluent-theme/src/components/preChatActivity/StarterPromptsCardAction.tsx @@ -0,0 +1,62 @@ +import { hooks } from 'botframework-webchat-component'; +import { type DirectLineCardAction } from 'botframework-webchat-core'; +import cx from 'classnames'; +import React, { memo, useCallback, useMemo } from 'react'; +import { useRefFrom } from 'use-ref-from'; +import { useStyles } from '../../styles/index.js'; +import MonochromeImageMasker from './private/MonochromeImageMasker.js'; +import styles from './StarterPromptsCardAction.module.css'; + +const { useFocus, useRenderMarkdownAsHTML, useSendBoxValue } = hooks; + +type Props = Readonly<{ + className?: string | undefined; + messageBackAction: DirectLineCardAction & { type: 'messageBack' }; +}>; + +const StarterPromptAction = ({ className, messageBackAction }: Props) => { + const [_, setSendBoxValue] = useSendBoxValue(); + const classNames = useStyles(styles); + const focus = useFocus(); + const inputTextRef = useRefFrom(messageBackAction.displayText || messageBackAction.text || ''); + const renderMarkdownAsHTML = useRenderMarkdownAsHTML('message activity'); + const subtitleHTML = useMemo( + () => (renderMarkdownAsHTML ? { __html: renderMarkdownAsHTML(messageBackAction.text || '') } : { __html: '' }), + [messageBackAction.text, renderMarkdownAsHTML] + ); + + const handleClick = useCallback(() => { + setSendBoxValue(inputTextRef.current); + + // Focus on the send box after the value is "pasted." + focus('sendBox'); + }, [focus, inputTextRef, setSendBoxValue]); + + return ( + + ); +}; + +StarterPromptAction.displayName = 'StarterPromptAction'; + +export default memo(StarterPromptAction); diff --git a/packages/fluent-theme/src/components/preChatActivity/StarterPromptsToolbar.module.css b/packages/fluent-theme/src/components/preChatActivity/StarterPromptsToolbar.module.css new file mode 100644 index 0000000000..95b0d762b2 --- /dev/null +++ b/packages/fluent-theme/src/components/preChatActivity/StarterPromptsToolbar.module.css @@ -0,0 +1,18 @@ +:global(.webchat-fluent) .pre-chat-message-activity__card-action-toolbar { + container-name: webchat-container; + container-type: inline-size; +} + +:global(.webchat-fluent) .pre-chat-message-activity__card-action-toolbar-grid { + display: grid; + gap: var(--webchat-spacingHorizontalM); + grid-template-columns: 1fr 1fr 1fr; + padding: 0; +} + +/* TODO: What is the good width to show as 3 columns? Web Chat, by default, has a bubble max width of 480px. */ +@container webchat-container (width <= 480px) { + :global(.webchat-fluent) .pre-chat-message-activity__card-action-toolbar-grid { + grid-template-columns: 1fr; + } +} diff --git a/packages/fluent-theme/src/components/preChatActivity/StarterPromptsToolbar.tsx b/packages/fluent-theme/src/components/preChatActivity/StarterPromptsToolbar.tsx new file mode 100644 index 0000000000..9f04d0c1d8 --- /dev/null +++ b/packages/fluent-theme/src/components/preChatActivity/StarterPromptsToolbar.tsx @@ -0,0 +1,35 @@ +import { type DirectLineCardAction } from 'botframework-webchat-core'; +import cx from 'classnames'; +import React, { memo } from 'react'; +import { useStyles } from '../../styles/index.js'; +import StarterPromptsCardAction from './StarterPromptsCardAction.js'; +import styles from './StarterPromptsToolbar.module.css'; + +type Props = Readonly<{ + cardActions: readonly DirectLineCardAction[]; + className?: string | undefined; +}>; + +const StarterPrompts = ({ cardActions, className }: Props) => { + const classNames = useStyles(styles); + + return ( + // TODO: Accessibility-wise, this should be role="toolbar" with keyboard navigation. +
+
+ {cardActions + .filter( + (card: DirectLineCardAction): card is DirectLineCardAction & { type: 'messageBack' } => + card.type === 'messageBack' + ) + .map(cardAction => ( + + ))} +
+
+ ); +}; + +StarterPrompts.displayName = 'StarterPrompts'; + +export default memo(StarterPrompts); diff --git a/packages/fluent-theme/src/components/preChatActivity/index.tsx b/packages/fluent-theme/src/components/preChatActivity/index.tsx new file mode 100644 index 0000000000..445fee35b6 --- /dev/null +++ b/packages/fluent-theme/src/components/preChatActivity/index.tsx @@ -0,0 +1,2 @@ +export { default as isPreChatMessageActivity } from './isPreChatMessageActivity.js'; +export { default as PreChatMessageActivity } from './PreChatMessageActivity.js'; diff --git a/packages/fluent-theme/src/components/preChatActivity/isPreChatMessageActivity.ts b/packages/fluent-theme/src/components/preChatActivity/isPreChatMessageActivity.ts new file mode 100644 index 0000000000..344e85a60c --- /dev/null +++ b/packages/fluent-theme/src/components/preChatActivity/isPreChatMessageActivity.ts @@ -0,0 +1,26 @@ +import type { WebChatActivity } from 'botframework-webchat-core'; +import { array, literal, object, safeParse, string, type InferOutput } from 'valibot'; + +const messageEntity = object({ + '@context': literal('https://schema.org'), + '@id': literal(''), // Must be empty string. + '@type': literal('Message'), + keywords: array(string()), + type: literal('https://schema.org/Message') +}); + +type MessageEntity = InferOutput; + +export default function isPreChatMessageActivity( + activity: undefined | WebChatActivity +): activity is WebChatActivity & { type: 'message' } { + if (activity?.type !== 'message') { + return false; + } + + const message = activity.entities?.find( + (entity): entity is MessageEntity => safeParse(messageEntity, entity).success + ); + + return !!message?.keywords.includes('PreChatMessage'); +} diff --git a/packages/fluent-theme/src/components/preChatActivity/private/MonochromeImageMasker.module.css b/packages/fluent-theme/src/components/preChatActivity/private/MonochromeImageMasker.module.css new file mode 100644 index 0000000000..6c0b1c3ce9 --- /dev/null +++ b/packages/fluent-theme/src/components/preChatActivity/private/MonochromeImageMasker.module.css @@ -0,0 +1,5 @@ +:global(.webchat-fluent) .pre-chat-message-activity__monochrome-image-masker { + background-color: var(--webchat-colorNeutralForeground4); + mask-image: var(--mask-image); + --webkit-mask-image: var(--mask-image); +} diff --git a/packages/fluent-theme/src/components/preChatActivity/private/MonochromeImageMasker.tsx b/packages/fluent-theme/src/components/preChatActivity/private/MonochromeImageMasker.tsx new file mode 100644 index 0000000000..fa679f0772 --- /dev/null +++ b/packages/fluent-theme/src/components/preChatActivity/private/MonochromeImageMasker.tsx @@ -0,0 +1,19 @@ +import cx from 'classnames'; +import React, { memo, useMemo, type CSSProperties } from 'react'; +import { useStyles } from '../../../styles/index.js'; +import styles from './MonochromeImageMasker.module.css'; + +type Props = Readonly<{ className?: string | undefined; src: string }>; + +const MonochromeImageMasker = ({ className, src }: Props) => { + const classNames = useStyles(styles); + const style = useMemo(() => ({ '--mask-image': `url(${src})` }) as CSSProperties, [src]); + + return ( +
+ ); +}; + +MonochromeImageMasker.displayName = 'MonochromeImageMasker'; + +export default memo(MonochromeImageMasker); diff --git a/packages/fluent-theme/src/components/sendBox/SendBox.tsx b/packages/fluent-theme/src/components/sendBox/SendBox.tsx index e93065c40b..770afa17e3 100644 --- a/packages/fluent-theme/src/components/sendBox/SendBox.tsx +++ b/packages/fluent-theme/src/components/sendBox/SendBox.tsx @@ -183,7 +183,7 @@ function SendBox( />
- {!telephoneKeypadShown && maxMessageLength && ( + {!telephoneKeypadShown && maxMessageLength && isFinite(maxMessageLength) && (
{ focus('sendBox'); }, [focus]); - const children = suggestedActions.map((cardAction, index) => { - const { displayText, image, imageAltText, text, type, value } = cardAction as { - displayText?: string; - image?: string; - imageAltText?: string; - text?: string; - type: - | 'call' - | 'downloadFile' - | 'imBack' - | 'messageBack' - | 'openUrl' - | 'playAudio' - | 'playVideo' - | 'postBack' - | 'showImage' - | 'signin'; - value?: { [key: string]: any } | string; - }; + const children = isPreChatMessageActivity(activity) + ? [] // Do not show suggested actions for pre-chat message, suggested actions has already shown inlined. + : suggestedActions.map((cardAction, index) => { + const { displayText, image, imageAltText, text, type, value } = cardAction as { + displayText?: string; + image?: string; + imageAltText?: string; + text?: string; + type: + | 'call' + | 'downloadFile' + | 'imBack' + | 'messageBack' + | 'openUrl' + | 'playAudio' + | 'playVideo' + | 'postBack' + | 'showImage' + | 'signin'; + value?: { [key: string]: any } | string; + }; - if (!suggestedActions?.length) { - return null; - } + if (!suggestedActions?.length) { + return null; + } - return ( - - ); - }); + return ( + + ); + }); return ( diff --git a/packages/fluent-theme/src/components/theme/Theme.module.css b/packages/fluent-theme/src/components/theme/Theme.module.css index a9852058b5..a7f8b004da 100644 --- a/packages/fluent-theme/src/components/theme/Theme.module.css +++ b/packages/fluent-theme/src/components/theme/Theme.module.css @@ -1,4 +1,3 @@ - :global(.webchat-fluent).theme { display: contents; @@ -16,10 +15,15 @@ --webchat-colorNeutralBackground4: var(--colorNeutralBackground4, #f0f0f0); --webchat-colorNeutralBackground5: var(--colorNeutralBackground5, #ebebeb); + --webchat-colorNeutralBackground1Disabled: var(--colorNeutralBackground1Disabled, #f0f0f0); + --webchat-colorNeutralBackground1Pressed: var(--colorNeutralBackground1Pressed, #e0e0e0); + + --webchat-colorNeutralGrey94: var(--colorNeutralGrey94, #f0f0f0); + --webchat-colorNeutralStroke1: var(--colorNeutralStroke1, #d1d1d1); --webchat-colorNeutralStroke2: var(--colorNeutralStroke2, #e0e0e0); --webchat-colorNeutralStroke1Selected: var(--colorNeutralStroke1Selected, #bdbdbd); - + --webchat-colorStrokeFocus2: var(--colorStrokeFocus2, #000000); --webchat-colorBrandStroke2: var(--colorBrandStroke2, #9edcf7); @@ -30,7 +34,7 @@ --webchat-colorBrandForegroundLink: var(--colorBrandForegroundLink, #01678c); --webchat-colorBrandForegroundLinkHover: var(--colorBrandForegroundLinkHover, #015a7a); --webchat-colorBrandForegroundLinkPressed: var(--colorBrandForegroundLinkPressed, #014259); - --webchat-colorBrandForegroundLinkSelected: var(--colorBrandForegroundLinkSelected, #01678c); + --webchat-colorBrandForegroundLinkSelected: var(--colorBrandForegroundLinkSelected, #01678c); --webchat-colorBrandBackground2Hover: var(--colorBrandBackground2Hover, #bee7fa); --webchat-colorBrandBackground2Pressed: var(--colorBrandBackground2Pressed, #7fd2f5); @@ -55,14 +59,25 @@ /* https://github.com/microsoft/fluentui/blob/master/packages/tokens/src/global/spacings.ts */ --webchat-spacingHorizontalMNudge: var(--spacingHorizontalMNudge, 10px); + --webchat-spacingHorizontalM: var(--spacingHorizontalM, 12px); + --webchat-spacingHorizontalXXXL: var(--spacingHorizontalXXXL, 32px); + --webchat-spacingVerticalL: var(--spacingVerticalL, 16px); /* https://github.com/microsoft/fluentui/blob/master/packages/tokens/src/global/fonts.ts */ --webchat-fontFamilyBase: var(--fontFamilyBase, 'Segoe UI', 'Segoe UI Web (West European)', -apple-system, BlinkMacSystemFont, Roboto, 'Helvetica Neue', sans-serif); --webchat-fontFamilyNumeric: var(--fontFamilyNumeric, Bahnschrift, 'Segoe UI', 'Segoe UI Web (West European)', -apple-system, BlinkMacSystemFont, Roboto, 'Helvetica Neue', sans-serif); /* https://github.com/microsoft/fluentui/blob/master/packages/tokens/src/global/fonts.ts */ + --webchat-fontWeightRegular: var(--fontWeightRegular, 400); --webchat-fontWeightSemibold: var(--fontWeightSemibold, 600); - + + /* https://github.com/microsoft/fluentui/blob/master/packages/tokens/src/global/fonts.ts */ + --webchat-fontSizeBase300: var(--fontSizeBase300, 14px); + --webchat-fontSizeHero700: var(--fontSizeHero700, 28px); + + --webchat-lineHeightBase300: var(--lineHeightBase300, 20px); + --webchat-lineHeightHero700: var(--lineHeightHero700, 36px); + --webchat-strokeWidthThicker: var(--strokeWidthThicker, 3px); --webchat-durationUltraFast: var(--durationUltraFast, 0); @@ -70,6 +85,9 @@ --webchat-curveAccelerateMid: var(--curveAccelerateMid, cubic-bezier(1,0,1,1)); --webchat-curveDecelerateMid: var(--curveDecelerateMid, cubic-bezier(0,0,0,1)); + + /* https://github.com/microsoft/fluentui/blob/master/packages/tokens/src/utils/shadows.ts */ + --webchat-shadow2: 0 0 2px rgba(0, 0, 0, 12%), 0 1px 2px rgba(0, 0, 0, 14%); } @media (prefers-reduced-motion) { @@ -77,4 +95,4 @@ --webchat-durationUltraFast: 0.01ms; --webchat-durationNormal: 0.01ms; } -} \ No newline at end of file +} diff --git a/packages/fluent-theme/src/components/theme/Theme.tsx b/packages/fluent-theme/src/components/theme/Theme.tsx index 6b9d9a1b34..8465bf0182 100644 --- a/packages/fluent-theme/src/components/theme/Theme.tsx +++ b/packages/fluent-theme/src/components/theme/Theme.tsx @@ -5,7 +5,7 @@ import { useStyles } from '../../styles'; export const rootClassName = 'webchat-fluent'; -export default function WebchatTheme(props: Readonly<{ readonly children: ReactNode | undefined }>) { +export default function Theme(props: Readonly<{ readonly children: ReactNode | undefined }>) { const classNames = useStyles(styles); return
{props.children}
; } diff --git a/packages/fluent-theme/src/private/FluentThemeProvider.tsx b/packages/fluent-theme/src/private/FluentThemeProvider.tsx index bbd4bfb5af..77614405e2 100644 --- a/packages/fluent-theme/src/private/FluentThemeProvider.tsx +++ b/packages/fluent-theme/src/private/FluentThemeProvider.tsx @@ -1,20 +1,37 @@ import { Components } from 'botframework-webchat-component'; import React, { memo, type ReactNode } from 'react'; +import type { ActivityMiddleware } from 'botframework-webchat-api'; +import { isPreChatMessageActivity, PreChatMessageActivity } from '../components/preChatActivity'; +import { SendBox } from '../components/sendBox'; import { TelephoneKeypadProvider } from '../components/telephoneKeypad'; import { WebChatTheme } from '../components/theme'; -import { SendBox } from '../components/sendBox'; const { ThemeProvider } = Components; type Props = Readonly<{ children?: ReactNode | undefined }>; +const activityMiddleware: ActivityMiddleware[] = [ + () => + next => + (...args) => { + const activity = args[0]?.activity; + + if (activity && isPreChatMessageActivity(activity)) { + return () => ; + } + + return next(...args); + } +]; const sendBoxMiddleware = [() => () => () => SendBox]; const FluentThemeProvider = ({ children }: Props) => ( - {children} + + {children} + );