diff --git a/CHANGELOG.md b/CHANGELOG.md
index 135d3f4bf9..58064fcd3f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -313,7 +313,7 @@ Breaking changes in this release:
- Bumped Chrome in Docker to 141 from 110, in PR [#5619](https://github.com/microsoft/BotFramework-WebChat/pull/5619), by [@compulim](https://github.com/compulim)
- Bumped to [`valibot@1.2.0`](https://npmjs.com/package/valibot/v/1.2.0), in PR [#5650](https://github.com/microsoft/BotFramework-WebChat/pull/5650), by [@compulim](https://github.com/compulim)
- Pinned to [`botframework-directlinespeech-sdk@4.18.1-main.20251208.8ccadd6`](https://npmjs.com/package/botframework-directlinespeech-sdk/v/4.18.1-main.20251208.8ccadd6), by [@OEvgeny](https://github.com/OEvgeny) in PR [#5662](https://github.com/microsoft/BotFramework-WebChat/pull/5662)
-- Converted remaining activity components to CSS Modules, in PR [#5668](https://github.com/microsoft/BotFramework-WebChat/pull/5668), by [@OEvgeny](https://github.com/OEvgeny)
+- Converted remaining activity components to CSS Modules, in PR [#5668](https://github.com/microsoft/BotFramework-WebChat/pull/5668), in PR [#5669](https://github.com/microsoft/BotFramework-WebChat/pull/5669), by [@OEvgeny](https://github.com/OEvgeny)
### Deprecated
diff --git a/__tests__/html2/citation/claimInterpreter/dangerousLink.html b/__tests__/html2/citation/claimInterpreter/dangerousLink.html
index cc8dcd60f7..908ea753ec 100644
--- a/__tests__/html2/citation/claimInterpreter/dangerousLink.html
+++ b/__tests__/html2/citation/claimInterpreter/dangerousLink.html
@@ -65,7 +65,7 @@
expect(markdownLinks[0].getAttribute('href')).toBe('https://aka.ms/claim');
- const claimInterpreterElement = pageElements.activities()[0].querySelector('.webchat__activity-status__originator');
+ const claimInterpreterElement = pageElements.activities()[0].querySelector('.activity-status__originator');
expect(claimInterpreterElement).toHaveProperty('tagName', 'SPAN');
expect(claimInterpreterElement).toHaveProperty('textContent', 'Surfaced with Azure OpenAI');
diff --git a/packages/component/src/ActivityFeedback/private/ThumbButton.module.css b/packages/component/src/ActivityFeedback/private/ThumbButton.module.css
new file mode 100644
index 0000000000..d9efcced9a
--- /dev/null
+++ b/packages/component/src/ActivityFeedback/private/ThumbButton.module.css
@@ -0,0 +1,91 @@
+:global(.webchat) .thumb-button {
+ align-items: center;
+ border-radius: 2px;
+ box-sizing: content-box;
+ display: grid;
+ grid-template-areas: 'main';
+ height: 16px;
+ width: 16px;
+
+ .thumb-button__input {
+ appearance: none;
+ background: transparent;
+ border: 0;
+ grid-area: main;
+ height: 16px;
+ margin: 0;
+ opacity: 0;
+ padding: 0;
+ width: 16px;
+
+ &:active {
+ background: #edebe9;
+ }
+ }
+
+ &:has(.thumb-button__input:focus-visible) {
+ outline: solid 1px #605e5c; /* has opacity of 0, we need to set the outline in the container. */
+ }
+
+ .thumb-button__image {
+ color: var(--webchat__color--accent);
+ grid-area: main;
+ justify-self: center; /* Unsure why "justifyContent" doesn't work. */
+ pointer-events: none;
+ visibility: hidden;
+ width: 14px;
+
+ &.thumb-button__image--is-stroked {
+ visibility: unset;
+ }
+ }
+
+ &:has(.thumb-button__input:is(:not([aria-disabled='true']):hover, :checked, [aria-pressed='true'])) {
+ .thumb-button__image {
+ &.thumb-button__image--is-stroked {
+ visibility: hidden;
+ }
+
+ &.thumb-button__image--is-filled {
+ visibility: unset;
+ }
+ }
+ }
+
+ &.thumb-button--large {
+ border: 1px solid transparent;
+ border-radius: 4px;
+ height: 30px;
+ width: 30px;
+
+ .thumb-button__input {
+ background: currentColor;
+ height: 30px;
+ width: 30px;
+ }
+
+ .thumb-button__image {
+ color: currentColor;
+ font-size: 20px;
+ height: 1em;
+ width: 1em;
+ }
+
+ &:has(.thumb-button__input:is(:hover, :active, :checked, [aria-pressed='true'])) .thumb-button__image {
+ color: var(--webchat__color--accent);
+ }
+
+ &:has(.thumb-button__input[aria-disabled='true']) .thumb-button__image {
+ color: var(--webchat__color--subtle);
+ }
+
+ &.thumb-button--has-submitted:has(.thumb-button__input:not(:checked):not([aria-pressed='true']))
+ .thumb-button__tooltip {
+ display: none;
+ }
+
+ &.thumb-button--has-submitted .thumb-button__tooltip {
+ --webchat__tooltip-anchor-inline-start: 20%;
+ }
+ }
+}
diff --git a/packages/component/src/ActivityFeedback/private/ThumbButton.tsx b/packages/component/src/ActivityFeedback/private/ThumbButton.tsx
index 6ea6811772..54ff326bfa 100644
--- a/packages/component/src/ActivityFeedback/private/ThumbButton.tsx
+++ b/packages/component/src/ActivityFeedback/private/ThumbButton.tsx
@@ -1,15 +1,17 @@
import { validateProps } from '@msinternal/botframework-webchat-react-valibot';
+import { useStyles } from '@msinternal/botframework-webchat-styles/react';
import { hooks } from 'botframework-webchat-api';
-import classNames from 'classnames';
+import cx from 'classnames';
import React, { forwardRef, memo, useCallback, useMemo, type ForwardedRef, type KeyboardEventHandler } from 'react';
import { useRefFrom } from 'use-ref-from';
import { boolean, function_, literal, object, optional, pipe, readonly, string, union, type InferInput } from 'valibot';
-import useStyleSet from '../../hooks/useStyleSet';
import testIds from '../../testIds';
import { Tooltip } from '../../Tooltip';
import ThumbButtonImage from './ThumbButton.Image';
+import styles from './ThumbButton.module.css';
+
const { useLocalizer } = hooks;
const thumbButtonPropsSchema = pipe(
@@ -36,9 +38,9 @@ function ThumbButton(props: ThumbButtonProps, ref: ForwardedRef title ?? localize(direction === 'down' ? 'VOTE_DISLIKE_ALT' : 'VOTE_LIKE_ALT'),
@@ -54,25 +56,19 @@ function ThumbButton(props: ThumbButtonProps, ref: ForwardedRef
- {buttonTitle}
+ {buttonTitle}
);
}
diff --git a/packages/component/src/ActivityStatus/ActivityStatus.module.css b/packages/component/src/ActivityStatus/ActivityStatus.module.css
new file mode 100644
index 0000000000..ef9c226145
--- /dev/null
+++ b/packages/component/src/ActivityStatus/ActivityStatus.module.css
@@ -0,0 +1,31 @@
+:global(.webchat) .activity-status {
+ color: var(--webchat__color--timestamp);
+ font-family: var(--webchat__font--primary);
+ font-size: var(--webchat__font-size--small);
+ margin-block-start: calc(var(--webchat__padding--regular) / 2);
+
+ &.activity-status--slotted {
+ display: inline-flex;
+ gap: 4px;
+ }
+}
+
+:global(.webchat) .activity-status-slot {
+ display: contents;
+
+ &:not(:first-child)::before {
+ content: '|';
+ }
+
+ &:empty {
+ display: none;
+ }
+}
+
+:global(.webchat) .activity-status__originator {
+ align-items: center;
+
+ &.activity-status__originator--has-link {
+ color: var(--webchat__color--accent);
+ }
+}
diff --git a/packages/component/src/ActivityStatus/OthersActivityStatus.tsx b/packages/component/src/ActivityStatus/OthersActivityStatus.tsx
index fdf9f798a9..da35f46059 100644
--- a/packages/component/src/ActivityStatus/OthersActivityStatus.tsx
+++ b/packages/component/src/ActivityStatus/OthersActivityStatus.tsx
@@ -1,4 +1,5 @@
import { warnOnce } from '@msinternal/botframework-webchat-base/utils';
+import { useStyles } from '@msinternal/botframework-webchat-styles/react';
import { hooks } from 'botframework-webchat-api';
import {
getOrgSchemaMessage,
@@ -7,27 +8,28 @@ import {
parseClaim,
type WebChatActivity
} from 'botframework-webchat-core';
-import classNames from 'classnames';
+import cx from 'classnames';
import React, { memo, useMemo } from 'react';
import ActivityFeedback from '../ActivityFeedback/ActivityFeedback';
-import useStyleSet from '../hooks/useStyleSet';
import dereferenceBlankNodes from '../Utils/JSONLinkedData/dereferenceBlankNodes';
import Originator from './private/Originator';
import StatusSlot from './StatusSlot';
import Timestamp from './Timestamp';
+import styles from './ActivityStatus.module.css';
+
const { useStyleOptions } = hooks;
-type Props = Readonly<{ activity: WebChatActivity; className?: string | undefined }>;
+type Props = Readonly<{ activity: WebChatActivity; className?: string | undefined; slotted?: boolean }>;
const warnRootLevelThings = warnOnce(
'Root-level things are being deprecated, please relate all things to `entities[@id=""]` instead. This feature will be removed in 2025-03-06.'
);
-const OthersActivityStatus = memo(({ activity, className }: Props) => {
+const OthersActivityStatus = memo(({ activity, className, slotted }: Props) => {
const [{ feedbackActionsPlacement }] = useStyleOptions();
- const [{ sendStatus }] = useStyleSet();
+ const classNames = useStyles(styles);
const { timestamp } = activity;
const graph = useMemo(() => dereferenceBlankNodes(activity.entities || []), [activity.entities]);
@@ -60,7 +62,9 @@ const OthersActivityStatus = memo(({ activity, className }: Props) => {
}, [graph, messageThing]);
return (
-
+
{timestamp && (
diff --git a/packages/component/src/ActivityStatus/SelfActivityStatus.tsx b/packages/component/src/ActivityStatus/SelfActivityStatus.tsx
index b4a2936509..78b72bae9b 100644
--- a/packages/component/src/ActivityStatus/SelfActivityStatus.tsx
+++ b/packages/component/src/ActivityStatus/SelfActivityStatus.tsx
@@ -1,19 +1,21 @@
+import { useStyles } from '@msinternal/botframework-webchat-styles/react';
import { type WebChatActivity } from 'botframework-webchat-core';
-import classNames from 'classnames';
+import cx from 'classnames';
import React, { memo } from 'react';
import Timestamp from './Timestamp';
-import useStyleSet from '../hooks/useStyleSet';
-type Props = Readonly<{ activity: WebChatActivity; className?: string | undefined }>;
+import styleClassNames from './ActivityStatus.module.css';
-const SelftActivityStatus = memo(({ activity, className }: Props) => {
- const [{ sendStatus }] = useStyleSet();
+type Props = Readonly<{ activity: WebChatActivity; className?: string | undefined; slotted?: boolean }>;
+
+const SelftActivityStatus = memo(({ activity, className, slotted }: Props) => {
+ const classNames = useStyles(styleClassNames);
const { timestamp } = activity;
return timestamp ? (
diff --git a/packages/component/src/ActivityStatus/SendStatus/SendStatus.tsx b/packages/component/src/ActivityStatus/SendStatus/SendStatus.tsx
index 298db4635d..e5f14ad529 100644
--- a/packages/component/src/ActivityStatus/SendStatus/SendStatus.tsx
+++ b/packages/component/src/ActivityStatus/SendStatus/SendStatus.tsx
@@ -1,14 +1,15 @@
import { validateProps } from '@msinternal/botframework-webchat-react-valibot';
import { hooks } from 'botframework-webchat-api';
-import classNames from 'classnames';
+import { useStyles } from '@msinternal/botframework-webchat-styles/react';
import React, { memo, useCallback } from 'react';
import { any, literal, object, pipe, readonly, union, type InferInput } from 'valibot';
import useFocus from '../../hooks/useFocus';
-import useStyleSet from '../../hooks/useStyleSet';
import { SENDING, SEND_FAILED, SENT } from '../../types/internal/SendStatus';
import SendFailedRetry from './private/SendFailedRetry';
+import styles from '../ActivityStatus.module.css';
+
const { useLocalizer, usePostActivity } = hooks;
const sendStatusPropsSchema = pipe(
@@ -24,10 +25,10 @@ type SendStatusProps = InferInput;
function SendStatus(props: SendStatusProps) {
const { activity, sendStatus } = validateProps(sendStatusPropsSchema, props);
- const [{ sendStatus: sendStatusStyleSet }] = useStyleSet();
const focus = useFocus();
const localize = useLocalizer();
const postActivity = usePostActivity();
+ const classNames = useStyles(styles);
const handleRetryClick = useCallback(() => {
postActivity(activity);
@@ -40,7 +41,7 @@ function SendStatus(props: SendStatusProps) {
return (
-
+
{sendStatus === SENDING ? (
sendingText
) : sendStatus === SEND_FAILED ? (
diff --git a/packages/component/src/ActivityStatus/StatusSlot.tsx b/packages/component/src/ActivityStatus/StatusSlot.tsx
index c9a1fe4b54..1fa337f488 100644
--- a/packages/component/src/ActivityStatus/StatusSlot.tsx
+++ b/packages/component/src/ActivityStatus/StatusSlot.tsx
@@ -1,11 +1,16 @@
+import { useStyles } from '@msinternal/botframework-webchat-styles/react';
import React, { memo, ReactNode } from 'react';
-import classNames from 'classnames';
+import cx from 'classnames';
+
+import styles from './ActivityStatus.module.css';
type Props = Readonly<{ className?: string; children?: ReactNode | undefined }>;
-const StatusSlot = ({ children, className }: Props) => (
- {children}
-);
+const StatusSlot = ({ children, className }: Props) => {
+ const classNames = useStyles(styles);
+
+ return {children};
+};
StatusSlot.displayName = 'StatusSlot';
diff --git a/packages/component/src/ActivityStatus/private/Originator.tsx b/packages/component/src/ActivityStatus/private/Originator.tsx
index 1cfab0cf22..9b8fccdd18 100644
--- a/packages/component/src/ActivityStatus/private/Originator.tsx
+++ b/packages/component/src/ActivityStatus/private/Originator.tsx
@@ -1,10 +1,14 @@
import { validateProps } from '@msinternal/botframework-webchat-react-valibot';
+import { useStyles } from '@msinternal/botframework-webchat-styles/react';
import { type OrgSchemaProject } from 'botframework-webchat-core';
+import cx from 'classnames';
import React, { memo } from 'react';
import { custom, object, optional, pipe, readonly, safeParse, string, type InferInput } from 'valibot';
import useSanitizeHrefCallback from '../../hooks/internal/useSanitizeHrefCallback';
+import styles from '../ActivityStatus.module.css';
+
const originatorPropsSchema = pipe(
object({
// TODO: [P1] We should build this schema into `OrgSchemaProject` instead, or build a Schema.org query library.
@@ -33,6 +37,7 @@ const Originator = memo(function Originator(props: OriginatorProps) {
} = validateProps(originatorPropsSchema, props);
const sanitizeHref = useSanitizeHrefCallback();
+ const classNames = useStyles(styles);
const { sanitizedHref } = sanitizeHref(url);
const text = slogan || name;
@@ -41,7 +46,7 @@ const Originator = memo(function Originator(props: OriginatorProps) {
// Link is sanitized.
// eslint-disable-next-line react/forbid-elements
) : (
- {text}
+ {text}
);
});
diff --git a/packages/component/src/Middleware/ActivityStatus/createTimestampMiddleware.tsx b/packages/component/src/Middleware/ActivityStatus/createTimestampMiddleware.tsx
index cd34e75a08..5f66c360fd 100644
--- a/packages/component/src/Middleware/ActivityStatus/createTimestampMiddleware.tsx
+++ b/packages/component/src/Middleware/ActivityStatus/createTimestampMiddleware.tsx
@@ -20,9 +20,9 @@ export default function createTimestampMiddleware(): ActivityStatusMiddleware {
// If "hideTimestamp" is set, we will not render the visual timestamp. But continue to render the screen reader only version.
return ;
} else if (activity.from.role === 'bot') {
- return ;
+ return ;
}
- return ;
+ return ;
};
}
diff --git a/packages/fluent-theme/src/components/activity/PartGroupingDecorator.module.css b/packages/fluent-theme/src/components/activity/PartGroupingDecorator.module.css
index 4f9331e644..bf2c7ff50d 100644
--- a/packages/fluent-theme/src/components/activity/PartGroupingDecorator.module.css
+++ b/packages/fluent-theme/src/components/activity/PartGroupingDecorator.module.css
@@ -746,7 +746,7 @@
max-width: unset;
}
- :global(.webchat__activity-status) {
+ :global(.activity-status) {
margin: 0 0 var(--webchat-spacingHorizontalXXS);
}
diff --git a/packages/fluent-theme/src/components/theme/Theme.module.css b/packages/fluent-theme/src/components/theme/Theme.module.css
index 805cf2f13b..49ecae45c8 100644
--- a/packages/fluent-theme/src/components/theme/Theme.module.css
+++ b/packages/fluent-theme/src/components/theme/Theme.module.css
@@ -658,16 +658,17 @@
}
/* Feedback button */
-:global(.webchat-fluent).theme :global(.webchat__thumb-button) {
- :global(.webchat__thumb-button__image) {
+:global(.webchat-fluent).theme :global(.thumb-button),
+:global(.webchat-fluent).theme :global(.thumb-button.thumb-button--large) {
+ :global(.thumb-button__image) {
color: var(--webchat-colorNeutralForeground1);
}
- &:has(:global(.webchat__thumb-button__input):focus-visible) {
+ &:has(:global(.thumb-button__input):focus-visible) {
outline: var(--webchat-strokeWidthThick) solid var(--webchat-colorStrokeFocus2);
}
- &:has(:global(.webchat__thumb-button__input)[aria-disabled='true']) :global(.webchat__thumb-button__image) {
+ &:has(:global(.thumb-button__input)[aria-disabled='true']) :global(.thumb-button__image) {
color: var(--webchat-colorNeutralForegroundDisabled);
}
}