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); } }