diff --git a/__tests__/html/fluentTheme/side-by-side.wide.dark.html b/__tests__/html/fluentTheme/side-by-side.wide.dark.html
index 77e8176886..0b1a32e037 100644
--- a/__tests__/html/fluentTheme/side-by-side.wide.dark.html
+++ b/__tests__/html/fluentTheme/side-by-side.wide.dark.html
@@ -612,6 +612,49 @@
$ pip install numpy matplotlib`
}
+ ], [
+ {
+ from:{
+ role: "bot"
+ },
+ id: "a-00001",
+ type: "message",
+ text: "Hi! I'm Cody, the devbot. How can I help?",
+ timestamp: timestamp(),
+ entities: [
+ {
+ ...aiMessageEntity,
+ keywords: ['AIGeneratedContent', 'AllowCopy'],
+ potentialAction: [
+ {
+ "@type": "LikeAction",
+ actionStatus: "PotentialActionStatus",
+ target: {
+ "@type": "EntryPoint",
+ urlTemplate: "ms-directline://postback?interaction=like"
+ }
+ },
+ {
+ "@type": "DislikeAction",
+ actionStatus: "PotentialActionStatus",
+ result: {
+ "@type": "Review",
+ reviewBody: "I don't like it.",
+ "reviewBody-input": {
+ "@type": "PropertyValueSpecification",
+ valueMinLength: 3,
+ valueName: "reason"
+ }
+ },
+ target: {
+ "@type": "EntryPoint",
+ urlTemplate: "ms-directline://postback?interaction=dislike{&reason}"
+ }
+ }
+ ]
+ }
+ ]
+ }
]];
const leftStore = testHelpers.createStore();
@@ -726,6 +769,24 @@
await host.snapshot();
await host.sendKeys('ENTER');
await host.snapshot();
+ },
+ likeDislike: async sendbox => {
+ sendbox.focus();
+ await host.sendShiftTab();
+ await host.sendKeys('ARROW_UP');
+ await host.sendKeys('ENTER');
+ await host.snapshot();
+ await host.sendKeys('TAB');
+ await host.snapshot();
+ await host.sendKeys('ENTER');
+ await host.snapshot();
+ await host.sendKeys('TAB');
+ await host.snapshot();
+ await host.sendKeys('ENTER');
+ await host.snapshot();
+ await host.sendKeys('ESCAPE');
+ await host.sendKeys('ESCAPE');
+ await host.snapshot();
}
}));
diff --git a/__tests__/html/fluentTheme/side-by-side.wide.dark.js b/__tests__/html/fluentTheme/side-by-side.wide.dark.js
index 464c432f78..ef3cf60870 100644
--- a/__tests__/html/fluentTheme/side-by-side.wide.dark.js
+++ b/__tests__/html/fluentTheme/side-by-side.wide.dark.js
@@ -17,6 +17,10 @@ describe('Fluent theme applied', () => {
test('side by side left - transcript, right - codeblock', () =>
runHTML('fluentTheme/side-by-side.wide.dark?transcript=0&transcript=5&focus=1&focus-preset=viewCode'));
test('side by side left - transcript, right - codeblock dark', () =>
- runHTML('fluentTheme/side-by-side.wide.dark?transcript=0&transcript=5&focus=1&focus-preset=viewCode&codeBlockTheme=github-dark-default'));
+ runHTML(
+ 'fluentTheme/side-by-side.wide.dark?transcript=0&transcript=5&focus=1&focus-preset=viewCode&codeBlockTheme=github-dark-default'
+ ));
+ test('side by side left - transcript, right - feedback', () =>
+ runHTML('fluentTheme/side-by-side.wide.dark?transcript=0&transcript=6&focus=1&focus-preset=likeDislike'));
});
});
diff --git a/__tests__/html/fluentTheme/side-by-side.wide.html b/__tests__/html/fluentTheme/side-by-side.wide.html
index 9b907e4f81..8ca2ea848a 100644
--- a/__tests__/html/fluentTheme/side-by-side.wide.html
+++ b/__tests__/html/fluentTheme/side-by-side.wide.html
@@ -622,6 +622,49 @@
$ pip install numpy matplotlib`
}
+ ], [
+ {
+ from:{
+ role: "bot"
+ },
+ id: "a-00001",
+ type: "message",
+ text: "Hi! I'm Cody, the devbot. How can I help?",
+ timestamp: timestamp(),
+ entities: [
+ {
+ ...aiMessageEntity,
+ keywords: ['AIGeneratedContent', 'AllowCopy'],
+ potentialAction: [
+ {
+ "@type": "LikeAction",
+ actionStatus: "PotentialActionStatus",
+ target: {
+ "@type": "EntryPoint",
+ urlTemplate: "ms-directline://postback?interaction=like"
+ }
+ },
+ {
+ "@type": "DislikeAction",
+ actionStatus: "PotentialActionStatus",
+ result: {
+ "@type": "Review",
+ reviewBody: "I don't like it.",
+ "reviewBody-input": {
+ "@type": "PropertyValueSpecification",
+ valueMinLength: 3,
+ valueName: "reason"
+ }
+ },
+ target: {
+ "@type": "EntryPoint",
+ urlTemplate: "ms-directline://postback?interaction=dislike{&reason}"
+ }
+ }
+ ]
+ }
+ ]
+ }
]];
const leftStore = testHelpers.createStore();
@@ -709,6 +752,24 @@
await host.snapshot();
await host.sendKeys('ENTER');
await host.snapshot();
+ },
+ likeDislike: async sendbox => {
+ sendbox.focus();
+ await host.sendShiftTab();
+ await host.sendKeys('ARROW_UP');
+ await host.sendKeys('ENTER');
+ await host.snapshot();
+ await host.sendKeys('TAB');
+ await host.snapshot();
+ await host.sendKeys('ENTER');
+ await host.snapshot();
+ await host.sendKeys('TAB');
+ await host.snapshot();
+ await host.sendKeys('ENTER');
+ await host.snapshot();
+ await host.sendKeys('ESCAPE');
+ await host.sendKeys('ESCAPE');
+ await host.snapshot();
}
}));
diff --git a/__tests__/html/fluentTheme/side-by-side.wide.js b/__tests__/html/fluentTheme/side-by-side.wide.js
index 06f0a39000..544d83ecb1 100644
--- a/__tests__/html/fluentTheme/side-by-side.wide.js
+++ b/__tests__/html/fluentTheme/side-by-side.wide.js
@@ -16,5 +16,9 @@ describe('Fluent theme applied', () => {
test('side by side left - transcript, right - codeblock', () =>
runHTML('fluentTheme/side-by-side.wide?transcript=0&transcript=5&focus=1&focus-preset=viewCode'));
test('side by side left - transcript, right - codeblock dark', () =>
- runHTML('fluentTheme/side-by-side.wide?transcript=0&transcript=5&focus=1&focus-preset=viewCode&codeBlockTheme=github-dark-default'));
+ runHTML(
+ 'fluentTheme/side-by-side.wide?transcript=0&transcript=5&focus=1&focus-preset=viewCode&codeBlockTheme=github-dark-default'
+ ));
+ test('side by side left - transcript, right - feedback', () =>
+ runHTML('fluentTheme/side-by-side.wide?transcript=0&transcript=6&focus=1&focus-preset=likeDislike'));
});
diff --git a/packages/api/src/StyleOptions.ts b/packages/api/src/StyleOptions.ts
index 77a7c41687..0486772305 100644
--- a/packages/api/src/StyleOptions.ts
+++ b/packages/api/src/StyleOptions.ts
@@ -928,6 +928,18 @@ type StyleOptions = {
* New in 4.19.0.
*/
codeBlockTheme?: 'github-light-default' | 'github-dark-default';
+
+ /**
+ * Feedback actions placement
+ *
+ * - `'activity-actions'` - place feedback buttons inside activity actions
+ * - `'activity-status'` - place feedback buttons inside activity status
+ *
+ * @default 'activity-status'
+ *
+ * New in 4.19.0.
+ */
+ feedbackActionsPlacement?: 'activity-actions' | 'activity-status';
};
// StrictStyleOptions is only used internally in Web Chat and for simplifying our code:
diff --git a/packages/api/src/defaultStyleOptions.ts b/packages/api/src/defaultStyleOptions.ts
index ec3147fcbd..80d4d11649 100644
--- a/packages/api/src/defaultStyleOptions.ts
+++ b/packages/api/src/defaultStyleOptions.ts
@@ -305,7 +305,9 @@ const DEFAULT_OPTIONS: Required = {
borderAnimationColor2: '#4DD3FF',
borderAnimationColor3: '#2B8DD8',
- codeBlockTheme: 'github-light-default' as const
+ codeBlockTheme: 'github-light-default' as const,
+
+ feedbackActionsPlacement: 'activity-status' as const
};
export default DEFAULT_OPTIONS;
diff --git a/packages/component/src/Activity/ActivityFeedback.tsx b/packages/component/src/Activity/ActivityFeedback.tsx
new file mode 100644
index 0000000000..e405c7a036
--- /dev/null
+++ b/packages/component/src/Activity/ActivityFeedback.tsx
@@ -0,0 +1,53 @@
+import { hooks } from 'botframework-webchat-api';
+import { getOrgSchemaMessage, OrgSchemaAction, parseAction, WebChatActivity } from 'botframework-webchat-core';
+import cx from 'classnames';
+import React, { memo, useMemo } from 'react';
+
+import Feedback from './private/Feedback';
+import dereferenceBlankNodes from '../Utils/JSONLinkedData/dereferenceBlankNodes';
+
+const { useStyleOptions } = hooks;
+
+type ActivityFeedbackProps = Readonly<{
+ activity: WebChatActivity;
+ placement: 'activity-status' | 'activity-actions';
+}>;
+
+function ActivityFeedback({ activity, placement }: ActivityFeedbackProps) {
+ const [{ feedbackActionsPlacement }] = useStyleOptions();
+
+ const graph = useMemo(() => dereferenceBlankNodes(activity.entities || []), [activity.entities]);
+
+ const messageThing = useMemo(() => getOrgSchemaMessage(graph), [graph]);
+
+ const feedbackActions = useMemo | undefined>(() => {
+ try {
+ const reactActions = (messageThing?.potentialAction || []).filter(
+ ({ '@type': type }) => type === 'LikeAction' || type === 'DislikeAction'
+ );
+
+ if (reactActions.length) {
+ return Object.freeze(new Set(reactActions));
+ }
+
+ const voteActions = graph.filter(({ type }) => type === 'https://schema.org/VoteAction').map(parseAction);
+
+ if (voteActions.length) {
+ return Object.freeze(new Set(voteActions));
+ }
+ } catch {
+ // Intentionally left blank.
+ }
+ }, [graph, messageThing?.potentialAction]);
+
+ return feedbackActions?.size && placement === feedbackActionsPlacement ? (
+
+ ) : null;
+}
+
+export default memo(ActivityFeedback);
diff --git a/packages/component/src/ActivityStatus/private/Feedback/Feedback.tsx b/packages/component/src/Activity/private/Feedback.tsx
similarity index 89%
rename from packages/component/src/ActivityStatus/private/Feedback/Feedback.tsx
rename to packages/component/src/Activity/private/Feedback.tsx
index b5686dc31f..5ef6d82a30 100644
--- a/packages/component/src/ActivityStatus/private/Feedback/Feedback.tsx
+++ b/packages/component/src/Activity/private/Feedback.tsx
@@ -3,19 +3,20 @@ import { type OrgSchemaAction } from 'botframework-webchat-core';
import React, { Fragment, memo, useEffect, useState, type PropsWithChildren } from 'react';
import { useRefFrom } from 'use-ref-from';
-import FeedbackVoteButton from './private/VoteButton';
+import FeedbackVoteButton from './VoteButton';
const { usePonyfill, usePostActivity } = hooks;
type Props = Readonly<
PropsWithChildren<{
actions: ReadonlySet;
+ className?: string | undefined;
}>
>;
const DEBOUNCE_TIMEOUT = 500;
-const Feedback = memo(({ actions }: Props) => {
+const Feedback = memo(({ actions, className }: Props) => {
const [{ clearTimeout, setTimeout }] = usePonyfill();
const [selectedAction, setSelectedAction] = useState();
const postActivity = usePostActivity();
@@ -46,6 +47,7 @@ const Feedback = memo(({ actions }: Props) => {
{Array.from(actions).map((action, index) => (
void;
pressed?: boolean;
}>;
-const ThumbButton = memo(({ direction, onClick, pressed }: Props) => {
+const ThumbButton = memo(({ className, direction, onClick, pressed }: Props) => {
const [{ thumbButton }] = useStyleSet();
const localize = useLocalizer();
@@ -26,6 +27,7 @@ const ThumbButton = memo(({ direction, onClick, pressed }: Props) => {
className={classNames(
'webchat__thumb-button',
{ 'webchat__thumb-button--is-pressed': pressed },
+ className,
thumbButton + ''
)}
onClick={onClick}
diff --git a/packages/component/src/ActivityStatus/private/Feedback/private/icons/ThumbDislike16Filled.tsx b/packages/component/src/Activity/private/ThumbDislike16Filled.tsx
similarity index 100%
rename from packages/component/src/ActivityStatus/private/Feedback/private/icons/ThumbDislike16Filled.tsx
rename to packages/component/src/Activity/private/ThumbDislike16Filled.tsx
diff --git a/packages/component/src/ActivityStatus/private/Feedback/private/icons/ThumbDislike16Regular.tsx b/packages/component/src/Activity/private/ThumbDislike16Regular.tsx
similarity index 100%
rename from packages/component/src/ActivityStatus/private/Feedback/private/icons/ThumbDislike16Regular.tsx
rename to packages/component/src/Activity/private/ThumbDislike16Regular.tsx
diff --git a/packages/component/src/ActivityStatus/private/Feedback/private/icons/ThumbLike16Filled.tsx b/packages/component/src/Activity/private/ThumbLike16Filled.tsx
similarity index 100%
rename from packages/component/src/ActivityStatus/private/Feedback/private/icons/ThumbLike16Filled.tsx
rename to packages/component/src/Activity/private/ThumbLike16Filled.tsx
diff --git a/packages/component/src/ActivityStatus/private/Feedback/private/icons/ThumbLike16Regular.tsx b/packages/component/src/Activity/private/ThumbLike16Regular.tsx
similarity index 100%
rename from packages/component/src/ActivityStatus/private/Feedback/private/icons/ThumbLike16Regular.tsx
rename to packages/component/src/Activity/private/ThumbLike16Regular.tsx
diff --git a/packages/component/src/ActivityStatus/private/Feedback/private/VoteButton.tsx b/packages/component/src/Activity/private/VoteButton.tsx
similarity index 81%
rename from packages/component/src/ActivityStatus/private/Feedback/private/VoteButton.tsx
rename to packages/component/src/Activity/private/VoteButton.tsx
index c3bf888a54..d6e0ace2ef 100644
--- a/packages/component/src/ActivityStatus/private/Feedback/private/VoteButton.tsx
+++ b/packages/component/src/Activity/private/VoteButton.tsx
@@ -5,12 +5,13 @@ import { useRefFrom } from 'use-ref-from';
import ThumbsButton from './ThumbButton';
type Props = Readonly<{
+ className?: string;
action: OrgSchemaAction;
onClick?: (action: OrgSchemaAction) => void;
pressed: boolean;
}>;
-const FeedbackVoteButton = memo(({ action, onClick, pressed }: Props) => {
+const FeedbackVoteButton = memo(({ action, className, onClick, pressed }: Props) => {
const onClickRef = useRefFrom(onClick);
const voteActionRef = useRefFrom(action);
@@ -28,7 +29,7 @@ const FeedbackVoteButton = memo(({ action, onClick, pressed }: Props) => {
const handleClick = useCallback(() => onClickRef.current?.(voteActionRef.current), [onClickRef, voteActionRef]);
- return ;
+ return ;
});
FeedbackVoteButton.displayName = 'FeedbackVoteButton';
diff --git a/packages/component/src/ActivityStatus/OthersActivityStatus.tsx b/packages/component/src/ActivityStatus/OthersActivityStatus.tsx
index 6ab15ed222..cdc9ef181f 100644
--- a/packages/component/src/ActivityStatus/OthersActivityStatus.tsx
+++ b/packages/component/src/ActivityStatus/OthersActivityStatus.tsx
@@ -1,6 +1,5 @@
import {
getOrgSchemaMessage,
- OrgSchemaAction,
OrgSchemaProject,
parseAction,
parseClaim,
@@ -8,14 +7,14 @@ import {
type WebChatActivity
} from 'botframework-webchat-core';
import classNames from 'classnames';
-import React, { memo, useMemo, type ReactNode } from 'react';
+import React, { memo, useMemo } from 'react';
import useStyleSet from '../hooks/useStyleSet';
import dereferenceBlankNodes from '../Utils/JSONLinkedData/dereferenceBlankNodes';
-import Feedback from './private/Feedback/Feedback';
import Originator from './private/Originator';
-import Slotted from './Slotted';
import Timestamp from './Timestamp';
+import ActivityFeedback from '../Activity/ActivityFeedback';
+import StatusSlot from './StatusSlot';
type Props = Readonly<{ activity: WebChatActivity }>;
@@ -56,38 +55,14 @@ const OthersActivityStatus = memo(({ activity }: Props) => {
}
}, [graph, messageThing]);
- const feedbackActions = useMemo | undefined>(() => {
- try {
- const reactActions = (messageThing?.potentialAction || []).filter(
- ({ '@type': type }) => type === 'LikeAction' || type === 'DislikeAction'
- );
-
- if (reactActions.length) {
- return Object.freeze(new Set(reactActions));
- }
-
- const voteActions = graph.filter(({ type }) => type === 'https://schema.org/VoteAction').map(parseAction);
-
- if (voteActions.length) {
- return Object.freeze(new Set(voteActions));
- }
- } catch {
- // Intentionally left blank.
- }
- }, [graph, messageThing]);
-
return (
-
- {useMemo(
- () =>
- [
- timestamp && ,
- claimInterpreter && ,
- feedbackActions?.size &&
- ].filter(Boolean),
- [claimInterpreter, timestamp, feedbackActions]
- )}
-
+
+
{timestamp && }
+
{claimInterpreter && }
+
+
+
+
);
});
diff --git a/packages/component/src/ActivityStatus/SelfActivityStatus.tsx b/packages/component/src/ActivityStatus/SelfActivityStatus.tsx
index 4b2b51b8d2..632fdb6134 100644
--- a/packages/component/src/ActivityStatus/SelfActivityStatus.tsx
+++ b/packages/component/src/ActivityStatus/SelfActivityStatus.tsx
@@ -2,7 +2,6 @@ import { type WebChatActivity } from 'botframework-webchat-core';
import classNames from 'classnames';
import React, { memo } from 'react';
-import Slotted from './Slotted';
import Timestamp from './Timestamp';
import useStyleSet from '../hooks/useStyleSet';
@@ -13,9 +12,9 @@ const SelftActivityStatus = memo(({ activity }: Props) => {
const { timestamp } = activity;
return timestamp ? (
-
+
-
+
) : null;
});
diff --git a/packages/component/src/ActivityStatus/Slotted.tsx b/packages/component/src/ActivityStatus/Slotted.tsx
deleted file mode 100644
index 1c0506eca0..0000000000
--- a/packages/component/src/ActivityStatus/Slotted.tsx
+++ /dev/null
@@ -1,26 +0,0 @@
-import React, { Children, Fragment, memo, type PropsWithChildren } from 'react';
-import classNames from 'classnames';
-
-type Props = Readonly>;
-
-const Slotted = memo(({ children, className }: Props) => (
-
- {Children.map(children, (child, index) =>
- // TODO: We may be able to do this in pure CSS, say, :not(:first-child)::before { content: '|' }.
- index ? (
-
-
- {'|'}
-
- {child}
-
- ) : (
- child
- )
- )}
-
-));
-
-Slotted.displayName = 'SlottedActivityStatus';
-
-export default Slotted;
diff --git a/packages/component/src/ActivityStatus/StatusSlot.tsx b/packages/component/src/ActivityStatus/StatusSlot.tsx
new file mode 100644
index 0000000000..8252c6e28e
--- /dev/null
+++ b/packages/component/src/ActivityStatus/StatusSlot.tsx
@@ -0,0 +1,12 @@
+import React, { memo, ReactNode } from 'react';
+import classNames from 'classnames';
+
+type Props = Readonly<{ className?: string; children?: ReactNode | undefined }>;
+
+const StatusSlot = ({ children, className }: Props) => (
+ {children}
+);
+
+StatusSlot.displayName = 'StatusSlot';
+
+export default memo(StatusSlot);
diff --git a/packages/component/src/Attachment/Text/private/MarkdownTextContent.tsx b/packages/component/src/Attachment/Text/private/MarkdownTextContent.tsx
index b5bdbc2d73..7676cadf43 100644
--- a/packages/component/src/Attachment/Text/private/MarkdownTextContent.tsx
+++ b/packages/component/src/Attachment/Text/private/MarkdownTextContent.tsx
@@ -25,6 +25,7 @@ import MessageSensitivityLabel, { type MessageSensitivityLabelProps } from './Me
import isAIGeneratedActivity from './isAIGeneratedActivity';
import isBasedOnSoftwareSourceCode from './isBasedOnSoftwareSourceCode';
import isHTMLButtonElement from './isHTMLButtonElement';
+import ActivityFeedback from '../../../Activity/ActivityFeedback';
const { useLocalizer } = hooks;
@@ -244,6 +245,7 @@ const MarkdownTextContent = memo(({ activity, children, markdown }: Props) => {
{activity.type === 'message' && activity.text && messageThing?.keywords?.includes('AllowCopy') ? (
) : null}
+
);
diff --git a/packages/component/src/Styles/StyleSet/SendStatus.ts b/packages/component/src/Styles/StyleSet/SendStatus.ts
index 7d585747a9..4373f54c64 100644
--- a/packages/component/src/Styles/StyleSet/SendStatus.ts
+++ b/packages/component/src/Styles/StyleSet/SendStatus.ts
@@ -14,6 +14,17 @@ export default function createSendStatusStyle() {
gap: 4
},
+ '& .webchat__activity-status-slot': {
+ '&:not(:first-child)::before': {
+ content: `"|"`,
+ marginRight: '4px',
+ display: 'inline-block'
+ },
+ '&:empty': {
+ display: 'none'
+ }
+ },
+
'& .webchat__activity-status__originator': {
alignItems: 'center',
diff --git a/packages/component/src/Styles/StyleSet/ThumbButton.ts b/packages/component/src/Styles/StyleSet/ThumbButton.ts
index 7f5eba727e..d90722bc76 100644
--- a/packages/component/src/Styles/StyleSet/ThumbButton.ts
+++ b/packages/component/src/Styles/StyleSet/ThumbButton.ts
@@ -7,24 +7,48 @@ export default function () {
background: 'Transparent',
border: 0,
borderRadius: 2,
+ boxSizing: 'content-box',
height: 16,
+ fontSize: '14px',
/* The Fluent icon is larger than the button. We need to clip it.
Without clipping, hover effect will appear on the edge of the button but not possible to click. */
- overflow: 'hidden',
+ overflow: ['hidden', 'clip'],
padding: 0,
width: 16,
+ '&.webchat__thumb-button--large': {
+ alignItems: 'center',
+ border: '1px solid transparent',
+ borderRadius: '4px',
+ display: 'flex',
+ fontSize: '20px',
+ height: '20px',
+ justifyContent: 'center',
+ padding: '5px',
+ width: '20px',
+
+ '& .webchat__thumb-button__image': {
+ color: 'currentColor'
+ },
+
+ '&:hover, &:active, &.webchat__thumb-button--is-pressed': {
+ background: 'transparent',
+ color: CSSTokens.ColorAccent
+ }
+ },
+
'&:active': {
background: '#EDEBE9'
},
- '&:focus': {
+ '&:focus-visible': {
outline: 'solid 1px #605E5C'
},
'& .webchat__thumb-button__image': {
color: CSSTokens.ColorAccent,
- width: 14
+ width: '1em',
+ height: '1em'
},
'&:hover .webchat__thumb-button__image:not(.webchat__thumb-button__image--is-filled)': {
diff --git a/packages/fluent-theme/src/components/theme/Theme.module.css b/packages/fluent-theme/src/components/theme/Theme.module.css
index 351edb8c2a..86878f3963 100644
--- a/packages/fluent-theme/src/components/theme/Theme.module.css
+++ b/packages/fluent-theme/src/components/theme/Theme.module.css
@@ -590,3 +590,12 @@
:global(.webchat-fluent).theme :global(.webchat__monochrome-image-masker) {
background-color: var(--webchat-colorNeutralForeground4);
}
+
+/* Feedback button */
+:global(.webchat-fluent).theme :global(.webchat__thumb-button) {
+ color: var(--webchat-colorNeutralForeground1);
+
+ &:focus-visible {
+ outline: var(--webchat-strokeWidthThick) solid var(--webchat-colorStrokeFocus2);
+ }
+}
diff --git a/packages/fluent-theme/src/private/FluentThemeProvider.tsx b/packages/fluent-theme/src/private/FluentThemeProvider.tsx
index 7c1fcdfa6d..591926dffd 100644
--- a/packages/fluent-theme/src/private/FluentThemeProvider.tsx
+++ b/packages/fluent-theme/src/private/FluentThemeProvider.tsx
@@ -1,4 +1,4 @@
-import type { ActivityMiddleware } from 'botframework-webchat-api';
+import { type ActivityMiddleware, type StyleOptions } from 'botframework-webchat-api';
import { Components } from 'botframework-webchat-component';
import { WebChatDecorator } from 'botframework-webchat-component/decorator';
import React, { memo, type ReactNode } from 'react';
@@ -43,11 +43,20 @@ const sendBoxMiddleware = [() => () => () => PrimarySendBox];
const styles = createStyles();
+const fluentStyleOptions: StyleOptions = {
+ feedbackActionsPlacement: 'activity-actions'
+};
+
const FluentThemeProvider = ({ children, variant = 'fluent' }: Props) => (
-
+
{children}