diff --git a/CHANGELOG.md b/CHANGELOG.md index b0bacf65d0..135d3f4bf9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -313,6 +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) ### Deprecated diff --git a/__tests__/html2/accessibility/attachment/aria.html b/__tests__/html2/accessibility/attachment/aria.html index e0a0da03f5..abce308204 100644 --- a/__tests__/html2/accessibility/attachment/aria.html +++ b/__tests__/html2/accessibility/attachment/aria.html @@ -38,7 +38,7 @@ await pageConditions.scrollToBottomCompleted(); const carouselContainer = document - .querySelector('.webchat__carousel-filmstrip') + .querySelector('.carousel-filmstrip') .getAttribute('aria-labelledby'); expect(carouselContainer).toBeFalsy(); diff --git a/__tests__/html2/accessibility/attachment/stackedLayoutRole.html b/__tests__/html2/accessibility/attachment/stackedLayoutRole.html index 85d0ffb165..846f124db9 100644 --- a/__tests__/html2/accessibility/attachment/stackedLayoutRole.html +++ b/__tests__/html2/accessibility/attachment/stackedLayoutRole.html @@ -23,7 +23,7 @@ await pageConditions.minNumActivitiesShown(2); await pageConditions.scrollToBottomCompleted(); - const attachmentRole = document.querySelector('.webchat__carousel-filmstrip-attachment').getAttribute('role'); + const attachmentRole = document.querySelector('.carousel-filmstrip-attachment').getAttribute('role'); expect(attachmentRole).toBeTruthy(); }); diff --git a/__tests__/html2/accessibility/liveRegion/attachment/animationCard.html b/__tests__/html2/accessibility/liveRegion/attachment/animationCard.html index 36110130dd..8a575cc51a 100644 --- a/__tests__/html2/accessibility/liveRegion/attachment/animationCard.html +++ b/__tests__/html2/accessibility/liveRegion/attachment/animationCard.html @@ -51,7 +51,7 @@ await pageConditions.minNumActivitiesShown(2); // Replace the animation with a static image. - for (const imageElement of document.querySelectorAll('.webchat__bubble__content img')) { + for (const imageElement of document.querySelectorAll('.bubble__content img')) { imageElement.setAttribute( 'src', 'https://raw.githubusercontent.com/compulim/BotFramework-MockBot/master/public/assets/surface1.jpg' diff --git a/__tests__/html2/carousel/flipperButton.html b/__tests__/html2/carousel/flipperButton.html index ede4d40938..3a8eac17ab 100644 --- a/__tests__/html2/carousel/flipperButton.html +++ b/__tests__/html2/carousel/flipperButton.html @@ -23,8 +23,8 @@ await pageConditions.numActivitiesShown(2); // GIVEN: Carousel is at left most position. - const carouselLayout = document.querySelector('.webchat__carousel-layout'); - const carouselFilmstrip = carouselLayout.querySelector('.webchat__carousel-filmstrip'); + const carouselLayout = document.querySelector('.carousel-layout'); + const carouselFilmstrip = carouselLayout.querySelector('.carousel-filmstrip'); expect(carouselFilmstrip.scrollLeft).toBe(0); diff --git a/__tests__/html2/carousel/flipperButton.rtl.html b/__tests__/html2/carousel/flipperButton.rtl.html index 268995d66a..d7a832c3f7 100644 --- a/__tests__/html2/carousel/flipperButton.rtl.html +++ b/__tests__/html2/carousel/flipperButton.rtl.html @@ -24,8 +24,8 @@ await pageConditions.numActivitiesShown(2); // GIVEN: Carousel is at left most position. - const carouselLayout = document.querySelector('.webchat__carousel-layout'); - const carouselFilmstrip = carouselLayout.querySelector('.webchat__carousel-filmstrip'); + const carouselLayout = document.querySelector('.carousel-layout'); + const carouselFilmstrip = carouselLayout.querySelector('.carousel-filmstrip'); expect(carouselFilmstrip.scrollLeft).toBe(0); diff --git a/__tests__/html2/carousel/navigation.tab.html b/__tests__/html2/carousel/navigation.tab.html index e8095f8678..ea97187fef 100644 --- a/__tests__/html2/carousel/navigation.tab.html +++ b/__tests__/html2/carousel/navigation.tab.html @@ -55,7 +55,7 @@ ); await pageConditions.stabilized( 'carousel', - () => document.querySelector('.webchat__carousel-filmstrip').scrollLeft, + () => document.querySelector('.carousel-filmstrip').scrollLeft, 5, 5000 ); @@ -70,7 +70,7 @@ ); await pageConditions.stabilized( 'carousel', - () => document.querySelector('.webchat__carousel-filmstrip').scrollLeft, + () => document.querySelector('.carousel-filmstrip').scrollLeft, 5, 5000 ); diff --git a/__tests__/html2/fluentTheme/carousel.html b/__tests__/html2/fluentTheme/carousel.html index 5cdde1ea84..9c9b4e0334 100644 --- a/__tests__/html2/fluentTheme/carousel.html +++ b/__tests__/html2/fluentTheme/carousel.html @@ -55,8 +55,8 @@ await pageConditions.numActivitiesShown(2); // WHEN: Right flipper is clicked. - const carouselLayout = document.querySelector('.webchat__carousel-layout'); - const carouselFilmstrip = carouselLayout.querySelector('.webchat__carousel-filmstrip'); + const carouselLayout = document.querySelector('.carousel-layout'); + const carouselFilmstrip = carouselLayout.querySelector('.carousel-filmstrip'); const rightFlipper = carouselLayout.querySelector('[aria-label="Next"]'); // Improve test reliability by hover before click on flipper button. diff --git a/__tests__/html2/fluentTheme/carousel.html.snap-1.png b/__tests__/html2/fluentTheme/carousel.html.snap-1.png index 12128e85b6..078d6ac7bf 100644 Binary files a/__tests__/html2/fluentTheme/carousel.html.snap-1.png and b/__tests__/html2/fluentTheme/carousel.html.snap-1.png differ diff --git a/__tests__/html2/fluentTheme/carousel.html.snap-2.png b/__tests__/html2/fluentTheme/carousel.html.snap-2.png index ffd9734dc6..b90e8bd001 100644 Binary files a/__tests__/html2/fluentTheme/carousel.html.snap-2.png and b/__tests__/html2/fluentTheme/carousel.html.snap-2.png differ diff --git a/__tests__/html2/grouping/fluentTheme.html.snap-1.png b/__tests__/html2/grouping/fluentTheme.html.snap-1.png index 16011906d8..76ff1a0eae 100644 Binary files a/__tests__/html2/grouping/fluentTheme.html.snap-1.png and b/__tests__/html2/grouping/fluentTheme.html.snap-1.png differ diff --git a/__tests__/html2/middleware/cardAction/signIn.html b/__tests__/html2/middleware/cardAction/signIn.html index bb16b38bc4..3648f0a3f5 100644 --- a/__tests__/html2/middleware/cardAction/signIn.html +++ b/__tests__/html2/middleware/cardAction/signIn.html @@ -50,7 +50,7 @@ await pageConditions.numActivitiesShown(2); - const openUrlButton = document.querySelector('.webchat__bubble__content button'); + const openUrlButton = document.querySelector('.bubble__content button'); await host.click(openUrlButton); await pageConditions.numActivitiesShown(4); diff --git a/__tests__/html2/middleware/cardAction/signIn.noGetSessionId.html b/__tests__/html2/middleware/cardAction/signIn.noGetSessionId.html index 39a3c50b82..4aa34c5285 100644 --- a/__tests__/html2/middleware/cardAction/signIn.noGetSessionId.html +++ b/__tests__/html2/middleware/cardAction/signIn.noGetSessionId.html @@ -54,7 +54,7 @@ await pageConditions.numActivitiesShown(2); - const openUrlButton = document.querySelector('.webchat__bubble__content button'); + const openUrlButton = document.querySelector('.bubble__content button'); await host.click(openUrlButton); await pageConditions.numActivitiesShown(4); diff --git a/packages/component/src/Activity/Bubble.module.css b/packages/component/src/Activity/Bubble.module.css new file mode 100644 index 0000000000..6c157f084d --- /dev/null +++ b/packages/component/src/Activity/Bubble.module.css @@ -0,0 +1,131 @@ +:global(.webchat) .bubble { + display: flex; + position: relative; + + --webchat__bubble--background: var(--webchat__background--bubble); + --webchat__bubble--border-color: var(--webchat__border-color--bubble); + --webchat__bubble--border-radius: var(--webchat__border-radius--bubble); + --webchat__bubble--border-style: var(--webchat__border-style--bubble); + --webchat__bubble--border-width: var(--webchat__border-width--bubble); + --webchat__bubble--border-width: var(--webchat__border-width--bubble); + --webchat__bubble--color: var(--webchat__color--bubble); + --webchat__bubble--nub-size: var(--webchat__size--bubble-nub); + --webchat__bubble--nub-space: var(--webchat__space--bubble-nub); + + --webchat__bubble--nub-border-radius: min( + var(--webchat__bubble--border-radius), + calc(var(--webchat__bubble--nub-space) * -1) + ); + --webchat__bubble--nub-inset-block: auto calc(-1 * var(--webchat__bubble--nub-space)); + --webchat__bubble--nub-inset-inline: calc( + var(--webchat__bubble--border-width) - + var(--webchat__bubble--nub-size) + + var(--webchat__padding--regular) + ) + auto; + + &.bubble--nub-on-top { + --webchat__bubble--nub-border-radius: min(var(--webchat__bubble--border-radius), var(--webchat__bubble--nub-space)); + --webchat__bubble--nub-inset-block: var(--webchat__bubble--nub-space) auto; + } + &.bubble--from-user { + --webchat__bubble--background: var(--webchat__background--bubble-user); + --webchat__bubble--border-color: var(--webchat__border-color--bubble-user); + --webchat__bubble--border-radius: var(--webchat__border-radius--bubble-user); + --webchat__bubble--border-style: var(--webchat__border-style--bubble-user); + --webchat__bubble--border-width: var(--webchat__border-width--bubble-user); + --webchat__bubble--border-width: var(--webchat__border-width--bubble-user); + --webchat__bubble--color: var(--webchat__color--bubble-user); + --webchat__bubble--nub-size: var(--webchat__size--bubble-nub-user); + --webchat__bubble--nub-space: var(--webchat__space--bubble-nub-user); + + --webchat__bubble--nub-inset-inline: auto + calc(var(--webchat__bubble--border-width) - var(--webchat__bubble--nub-size) + var(--webchat__padding--regular)); + } + + .bubble__content { + flex-grow: 1; + margin-inline: 0; + /* This is for hiding content outside of the bubble, for example, content outside of border radius */ + overflow: hidden; + transition-duration: var(--webchat__transition-duration); + transition-property: margin-inline-start, margin-inline-end; + word-break: var(--webchat__word-break--message-activity); + } + + .bubble__nub { + overflow: hidden; /* This style is for IE11 because it doesn't respect SVG viewport */ + position: absolute; + } + + &:dir(rtl) .bubble__nub { + transform: scale(-1, 1); + } + + .bubble__nub-pad { + flex-shrink: 0; + transition-duration: var(--webchat__transition-duration); + transition-property: width; + width: 0; + } + + &.bubble--hide-nub, + &.bubble--show-nub { + .bubble__nub-pad { + width: var(--webchat__padding--regular); + } + } + + .bubble__content { + background: var(--webchat__bubble--background); + border-color: var(--webchat__bubble--border-color); + border-radius: var(--webchat__bubble--border-radius); + border-style: var(--webchat__bubble--border-style); + border-width: var(--webchat__bubble--border-width); + color: var(--webchat__bubble--color); + min-height: calc(var(--webchat__min-height--bubble) - var(--webchat__bubble--border-width) * 2); + } + + .bubble__nub { + height: var(--webchat__bubble--nub-size); + inset-block: var(--webchat__bubble--nub-inset-block); + inset-inline: var(--webchat__bubble--nub-inset-inline); + width: var(--webchat__bubble--nub-size); + } + + .bubble__nub-outline { + fill: var(--webchat__bubble--background); + stroke-width: var(--webchat__bubble--border-width); + stroke: var(--webchat__bubble--border-color); + } + + /* Bot bubble styles (not from user) - nub on inline-start side */ + &:not(.bubble--from-user) { + /* Adjust border radius when nub is shown */ + &.bubble--show-nub { + &:not(.bubble--nub-on-top) .bubble__content { + border-end-start-radius: var(--webchat__bubble--nub-border-radius); + } + + &.bubble--nub-on-top .bubble__content { + border-start-start-radius: var(--webchat__bubble--nub-border-radius); + } + } + } + + /* User bubble styles - nub on inline-end side */ + &.bubble--from-user { + flex-direction: row-reverse; + + /* Adjust border radius when nub is shown */ + &.bubble--show-nub { + &:not(.bubble--nub-on-top) .bubble__content { + border-end-end-radius: var(--webchat__bubble--nub-border-radius); + } + + &.bubble--nub-on-top .bubble__content { + border-start-end-radius: var(--webchat__bubble--nub-border-radius); + } + } + } +} diff --git a/packages/component/src/Activity/Bubble.tsx b/packages/component/src/Activity/Bubble.tsx index e71c8cf317..9e46346c43 100644 --- a/packages/component/src/Activity/Bubble.tsx +++ b/packages/component/src/Activity/Bubble.tsx @@ -1,36 +1,19 @@ /* eslint no-magic-numbers: ["error", { "ignore": [-1, 0, 1, 2, 10] }] */ import { reactNode, 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, { memo } from 'react'; import { boolean, literal, object, optional, pipe, readonly, string, union, type InferInput } from 'valibot'; -import { useStyleToEmotionObject } from '../hooks/internal/styleToEmotionObject'; -import useStyleSet from '../hooks/useStyleSet'; import isZeroOrPositive from '../Utils/isZeroOrPositive'; -const { useDirection, useStyleOptions } = hooks; +import styles from './Bubble.module.css'; -const ROOT_STYLE = { - '&.webchat__bubble': { - display: 'flex', - position: 'relative', +const { useStyleOptions } = hooks; - '& .webchat__bubble__nub-pad': { - flexShrink: 0 - }, - - '& .webchat__bubble__content': { - flexGrow: 1, - - // This is for hiding content outside of the bubble, for example, content outside of border radius - overflow: 'hidden' - } - } -}; - -function acuteNubSVG(nubSize, strokeWidth, side, upSideDown = false) { +function acuteNubSVG(nubSize, strokeWidth, side, upSideDown = false, classNames) { if (typeof nubSize !== 'number') { return false; } @@ -51,13 +34,13 @@ function acuteNubSVG(nubSize, strokeWidth, side, upSideDown = false) { return ( - + ); @@ -85,8 +68,6 @@ function Bubble(props: BubbleProps) { nub = false } = validateProps(bubblePropsSchema, props); - const [{ bubble: bubbleStyleSet }] = useStyleSet(); - const [direction] = useDirection(); const [ { bubbleBorderWidth, @@ -97,7 +78,7 @@ function Bubble(props: BubbleProps) { bubbleFromUserNubOffset } ] = useStyleOptions(); - const rootClassName = useStyleToEmotionObject()(ROOT_STYLE) + ''; + const classNames = useStyles(styles); const { borderWidth, nubOffset, nubSize, side } = fromUser ? { @@ -116,23 +97,20 @@ function Bubble(props: BubbleProps) { return (
-
-
{children}
- {nub === true && acuteNubSVG(nubSize, borderWidth, side, !isZeroOrPositive(nubOffset))} +
+
{children}
+ {nub === true && acuteNubSVG(nubSize, borderWidth, side, !isZeroOrPositive(nubOffset), classNames)}
); } diff --git a/packages/component/src/Activity/CarouselFilmStrip.js b/packages/component/src/Activity/CarouselFilmStrip.js index aabe0377a9..48e79421e3 100644 --- a/packages/component/src/Activity/CarouselFilmStrip.js +++ b/packages/component/src/Activity/CarouselFilmStrip.js @@ -1,8 +1,9 @@ /* eslint complexity: ["error", 30] */ import { hooks } from 'botframework-webchat-api'; +import { useStyles } from '@msinternal/botframework-webchat-styles/react'; import { useItemContainerCallbackRef, useScrollableCallbackRef } from 'react-film'; -import classNames from 'classnames'; +import cx from 'classnames'; import PropTypes from 'prop-types'; import React from 'react'; @@ -11,89 +12,10 @@ import CarouselFilmStripAttachment from './CarouselFilmStripAttachment'; import isZeroOrPositive from '../Utils/isZeroOrPositive'; import ScreenReaderText from '../ScreenReaderText'; import textFormatToContentType from '../Utils/textFormatToContentType'; -import useStyleSet from '../hooks/useStyleSet'; -import { useStyleToEmotionObject } from '../hooks/internal/styleToEmotionObject'; -const { useAvatarForBot, useAvatarForUser, useDirection, useLocalizer, useStyleOptions } = hooks; +import styles from './CarouselFilmStrip.module.css'; -const ROOT_STYLE = { - '&.webchat__carousel-filmstrip': { - display: 'flex', - flexDirection: 'column', - MsOverflowStyle: 'none', - overflowX: 'scroll', - overflowY: 'hidden', - position: 'relative', // This is to keep screen reader text in the destinated area. - touchAction: 'manipulation', - WebkitOverflowScrolling: 'touch', - - '&::-webkit-scrollbar': { - display: 'none' - }, - - '& .webchat__carousel-filmstrip__alignment-pad': { - flexShrink: 0 - }, - - '& .webchat__carousel-filmstrip-attachment': { - flex: 1 - }, - - '& .webchat__carousel-filmstrip__attachments': { - display: 'flex', - listStyleType: 'none', - margin: 0, - padding: 0 - }, - - '& .webchat__carousel-filmstrip__avatar': { - flexShrink: 0 - }, - - '& .webchat__carousel-filmstrip__avatar-gutter': { - display: 'flex', - flexDirection: 'column', - flexShrink: 0 - }, - - '& .webchat__carousel-filmstrip__complimentary': { - display: 'flex' - }, - - '& .webchat__carousel-filmstrip__complimentary-content': { - display: 'flex', - flexGrow: 1, - flexDirection: 'column' - }, - - '& .webchat__carousel-filmstrip__content': { - display: 'flex', - flexGrow: 1, - flexDirection: 'column' - }, - - '& .webchat__carousel-filmstrip__filler': { - flexGrow: 10000, - flexShrink: 1 - }, - - '& .webchat__carousel-filmstrip__main': { - display: 'flex' - }, - - '& .webchat__carousel-filmstrip__message': { - display: 'flex' - }, - - '& .webchat__carousel-filmstrip__nub-pad': { - flexShrink: 0 - }, - - '& .webchat__carousel-filmstrip__status': { - display: 'flex' - } - } -}; +const { useAvatarForBot, useAvatarForUser, useLocalizer, useStyleOptions } = hooks; const CarouselFilmStrip = ({ activity, @@ -105,13 +27,11 @@ const CarouselFilmStrip = ({ showCallout }) => { const [{ bubbleNubOffset, bubbleNubSize, bubbleFromUserNubOffset, bubbleFromUserNubSize }] = useStyleOptions(); - const [{ carouselFilmStrip: carouselFilmStripStyleSet }] = useStyleSet(); const [{ initials: botInitials }] = useAvatarForBot(); const [{ initials: userInitials }] = useAvatarForUser(); - const [direction] = useDirection(); const localize = useLocalizer(); - const rootClassName = useStyleToEmotionObject()(ROOT_STYLE) + ''; const showActivityStatus = typeof renderActivityStatus === 'function'; + const classNames = useStyles(styles); const itemContainerCallbackRef = useItemContainerCallbackRef(); const scrollableCallbackRef = useScrollableCallbackRef(); @@ -152,33 +72,32 @@ const CarouselFilmStrip = ({ return (
-
-
{showAvatar && renderAvatar({ activity })}
-
+
+
+ {showAvatar && renderAvatar({ activity })} +
+
{!!activityDisplayText && ( -
+
@@ -190,14 +109,14 @@ const CarouselFilmStrip = ({ } })} -
+
)} -
-
-
+
+
+
    {attachments.map((attachment, index) => ( @@ -220,12 +139,12 @@ const CarouselFilmStrip = ({
-
+
{showActivityStatus && ( -
-
-
+
+
+
{renderActivityStatus({ hideTimestamp })}
)} diff --git a/packages/component/src/Activity/CarouselFilmStrip.module.css b/packages/component/src/Activity/CarouselFilmStrip.module.css new file mode 100644 index 0000000000..db44ceea78 --- /dev/null +++ b/packages/component/src/Activity/CarouselFilmStrip.module.css @@ -0,0 +1,155 @@ +:global(.webchat) .carousel-filmstrip { + display: flex; + flex-direction: column; + -ms-overflow-style: none; + overflow-x: scroll; + overflow-y: hidden; + position: relative; /* This is to keep screen reader text in the destinated area. */ + touch-action: manipulation; + -webkit-overflow-scrolling: touch; + + /* Browser quirks: Firefox has no way to hide scrollbar and while keeping it in function */ + /* https://developer.mozilla.org/en-US/docs/Web/CSS/overflow */ + @supports (-moz-appearance: none) { + margin-block-end: -17px; + } + + &::-webkit-scrollbar { + display: none; + } + + .carousel-filmstrip__bubble { + max-width: var(--webchat__max-width--message-bubble); + min-width: var(--webchat__min-width--message-bubble); + transition-duration: var(--webchat__transition-duration); + transition-property: max-width; + } + + &.carousel-filmstrip--hide-nub, + &.carousel-filmstrip--show-nub, + &.carousel-filmstrip--hide-avatar, + &.carousel-filmstrip--show-avatar { + .carousel-filmstrip__bubble { + max-width: calc(var(--webchat__max-width--message-bubble) + var(--webchat__padding--regular)); + } + } + + .carousel-filmstrip__alignment-pad { + flex-shrink: 0; + transition-duration: var(--webchat__transition-duration); + transition-property: width; + width: var(--webchat__padding--regular); + } + + &.carousel-filmstrip--extra-trailing .carousel-filmstrip__alignment-pad { + width: calc(var(--webchat__padding--regular) * 2); + } + + &:not(.carousel-filmstrip--no-message) .carousel-filmstrip__attachments { + margin-block-start: var(--webchat__padding--regular); + } + + .carousel-filmstrip__attachments { + display: flex; + list-style-type: none; + margin: 0; + padding: 0; + } + + .carousel-filmstrip__avatar { + flex-shrink: 0; + } + + .carousel-filmstrip__avatar-gutter { + align-items: flex-end; + display: flex; + flex-direction: column; + flex-shrink: 0; + transition-duration: var(--webchat__transition-duration); + transition-property: width; + } + + &.carousel-filmstrip--hide-avatar, + &.carousel-filmstrip--show-avatar { + .carousel-filmstrip__avatar-gutter { + width: var(--webchat__size--avatar); + } + } + + &:not(.carousel-filmstrip--top-callout) .carousel-filmstrip__avatar-gutter { + justify-content: flex-end; + } + + .carousel-filmstrip__complimentary { + display: flex; + } + + .carousel-filmstrip__complimentary-content { + display: flex; + flex-grow: 1; + flex-direction: column; + } + + .carousel-filmstrip__content { + display: flex; + flex-grow: 1; + flex-direction: column; + } + + .carousel-filmstrip__filler { + flex-grow: 10000; + flex-shrink: 1; + } + + .carousel-filmstrip__main { + display: flex; + } + + .carousel-filmstrip__message { + display: flex; + } + + .carousel-filmstrip__nub-pad { + flex-shrink: 0; + transition-duration: var(--webchat__transition-duration); + transition-property: width; + width: 0; + } + + &.carousel-filmstrip--hide-avatar, + &.carousel-filmstrip--show-avatar, + &.carousel-filmstrip--hide-nub, + &.carousel-filmstrip--show-nub { + .carousel-filmstrip__nub-pad { + width: var(--webchat__padding--regular); + } + } + + .carousel-filmstrip__status { + display: flex; + } + + .carousel-filmstrip__avatar-gutter { + margin-inline-start: var(--webchat__padding--regular); + } + + &.carousel-filmstrip .carousel-filmstrip__attachments { + margin-inline-start: calc(-1 * var(--webchat__padding--regular)); + } + + &.carousel-filmstrip--hide-avatar, + &.carousel-filmstrip--show-avatar { + .carousel-filmstrip__attachments { + margin-inline-start: calc(-1 * (var(--webchat__size--avatar) + var(--webchat__padding--regular) * 2)); + } + } + + &.carousel-filmstrip--hide-nub, + &.carousel-filmstrip--show-nub { + &:not(.carousel-filmstrip--hide-avatar.carousel-filmstrip--show-avatar) { + .carousel-filmstrip__attachments { + margin-inline-start: calc(-1 * var(--webchat__padding--regular) * 2); + } + } + } +} diff --git a/packages/component/src/Activity/CarouselFilmStripAttachment.js b/packages/component/src/Activity/CarouselFilmStripAttachment.js index 853ba916f6..fc00369cdf 100644 --- a/packages/component/src/Activity/CarouselFilmStripAttachment.js +++ b/packages/component/src/Activity/CarouselFilmStripAttachment.js @@ -1,13 +1,15 @@ import { hooks } from 'botframework-webchat-api'; -import classNames from 'classnames'; +import { useStyles } from '@msinternal/botframework-webchat-styles/react'; +import cx from 'classnames'; import PropTypes from 'prop-types'; import React from 'react'; import Bubble from './Bubble'; import ScreenReaderText from '../ScreenReaderText'; -import useStyleSet from '../hooks/useStyleSet'; -const { useDirection, useLocalizer } = hooks; +import styles from './CarouselFilmStripAttachment.module.css'; + +const { useLocalizer } = hooks; const CarouselFilmStripAttachment = ({ activity, @@ -21,27 +23,24 @@ const CarouselFilmStripAttachment = ({ showAvatar, showNub }) => { - const [direction] = useDirection(); const localize = useLocalizer(); - const [{ carouselFilmStripAttachment: carouselFilmStripAttachmentStyleSet }] = useStyleSet(); + const classNames = useStyles(styles); const attachedAlt = localize(fromUser ? 'ACTIVITY_YOU_ATTACHED_ALT' : 'ACTIVITY_BOT_ATTACHED_ALT'); return (
  • {renderAttachment({ activity, attachment })} -
    +
  • ); diff --git a/packages/component/src/Activity/CarouselFilmStripAttachment.module.css b/packages/component/src/Activity/CarouselFilmStripAttachment.module.css new file mode 100644 index 0000000000..454851a066 --- /dev/null +++ b/packages/component/src/Activity/CarouselFilmStripAttachment.module.css @@ -0,0 +1,38 @@ +:global(.webchat) .carousel-filmstrip-attachment { + max-width: var(--webchat__max-width--attachment-bubble); + min-width: var(--webchat__min-width--attachment-bubble); + padding-inline-start: var(--webchat__padding--regular); + transition-duration: var(--webchat__transition-duration); + transition-property: max-width, min-width; + + &:focus { + outline: 0; + } + + &:focus .carousel-filmstrip-attachment--focus { + border-color: var(--webchat__color--transcript-visual-keyboard-indicator); + border-style: var(--webchat__border-style--transcript-visual-keyboard-indicator); + border-width: var(--webchat__border-width--transcript-visual-keyboard-indicator); + box-sizing: border-box; + height: calc(100% - var(--webchat__border-width--transcript-visual-keyboard-indicator)); + inset-block-start: 0; + inset-inline-start: 0; + pointer-events: none; + position: absolute; + width: calc(100% - var(--webchat__border-width--transcript-visual-keyboard-indicator)); + } + + &.carousel-filmstrip-attachment--hide-avatar, + &.carousel-filmstrip-attachment--show-avatar { + &:first-child { + padding-inline-start: calc(var(--webchat__size--avatar) + var(--webchat__padding--regular) * 2); + } + } + + &.carousel-filmstrip-attachment--hide-nub, + &.carousel-filmstrip-attachment--show-nub { + &:not(.carousel-filmstrip-attachment--hide-avatar.carousel-filmstrip-attachment--show-avatar):first-child { + padding-inline-start: calc(var(--webchat__padding--regular) * 2); + } + } +} diff --git a/packages/component/src/Activity/CarouselLayout.js b/packages/component/src/Activity/CarouselLayout.js index 1e5d3d9e74..10651e5a86 100644 --- a/packages/component/src/Activity/CarouselLayout.js +++ b/packages/component/src/Activity/CarouselLayout.js @@ -1,3 +1,4 @@ +import { useStyles } from '@msinternal/botframework-webchat-styles/react'; import { hooks } from 'botframework-webchat-api'; import { Composer as FilmComposer, @@ -8,23 +9,16 @@ import { useStyleSetClassNames as useReactFilmStyleSetClassNames } from 'react-film'; -import classNames from 'classnames'; +import cx from 'classnames'; import PropTypes from 'prop-types'; import React, { memo, useMemo } from 'react'; import CarouselFilmStrip from './CarouselFilmStrip'; import useNonce from '../hooks/internal/useNonce'; -import useStyleSet from '../hooks/useStyleSet'; -import { useStyleToEmotionObject } from '../hooks/internal/styleToEmotionObject'; -const { useDirection, useLocalizer, useStyleOptions } = hooks; +import styles from './CarouselLayout.module.css'; -const ROOT_STYLE = { - '&.webchat__carousel-layout': { - overflow: 'hidden', - position: 'relative' - } -}; +const { useDirection, useLocalizer, useStyleOptions } = hooks; const CarouselLayoutCore = ({ activity, @@ -34,7 +28,6 @@ const CarouselLayoutCore = ({ renderAvatar, showCallout }) => { - const [{ carouselFlipper: carouselFlipperStyleSet }] = useStyleSet(); const [{ root: filmRootClassName }] = useReactFilmStyleSetClassNames(); const [direction] = useDirection(); const [scrollBarWidth] = useScrollBarWidth(); @@ -42,7 +35,7 @@ const CarouselLayoutCore = ({ const leftSideFlipper = direction === 'rtl' ? '>' : '<'; const localize = useLocalizer(); const rightSideFlipper = direction === 'rtl' ? '<' : '>'; - const rootClassName = useStyleToEmotionObject()(ROOT_STYLE) + ''; + const classNames = useStyles(styles); const nextAlt = localize('CAROUSEL_FLIPPER_NEXT_ALT'); const previousAlt = localize('CAROUSEL_FLIPPER_PREVIOUS_ALT'); @@ -51,10 +44,8 @@ const CarouselLayoutCore = ({ const rightFlipperAriaLabel = direction === 'rtl' ? previousAlt : nextAlt; return ( -
    -
    +
    +
    ; const SayAlt = (props: SayAltProps) => { const { speak } = validateProps(sayAltPropsSchema, props); - const rootClassName = useStyleToEmotionObject()(ROOT_STYLE) + ''; + const classNames = useStyles(styles); - return !!speak &&
    {speak}
    ; + return !!speak &&
    {speak}
    ; }; export default memo(SayAlt); diff --git a/packages/component/src/Activity/StackedLayoutRoot.tsx b/packages/component/src/Activity/StackedLayoutRoot.tsx index 09e12b9855..19e0499d1c 100644 --- a/packages/component/src/Activity/StackedLayoutRoot.tsx +++ b/packages/component/src/Activity/StackedLayoutRoot.tsx @@ -47,7 +47,7 @@ const StackedLayoutRoot = memo((props: StackedLayoutRootProps) => { return (
    >; export default CustomPropertyNames; diff --git a/packages/fluent-theme/src/components/activity/PartGroupingDecorator.module.css b/packages/fluent-theme/src/components/activity/PartGroupingDecorator.module.css index f5f4bc8c7d..4f9331e644 100644 --- a/packages/fluent-theme/src/components/activity/PartGroupingDecorator.module.css +++ b/packages/fluent-theme/src/components/activity/PartGroupingDecorator.module.css @@ -43,7 +43,7 @@ /* #endregion */ /* #region Generated badge & attachments */ - :global(.stacked-layout .webchat__bubble .webchat__text-content__generated-badge) { + :global(.stacked-layout .bubble .webchat__text-content__generated-badge) { align-items: center; align-self: flex-start; background-color: var(--webchat-colorNeutralBackground5); @@ -70,7 +70,7 @@ } } - :global(.stacked-layout .webchat__bubble .webchat__fileContent__badge) { + :global(.stacked-layout .bubble .webchat__fileContent__badge) { cursor: default; font-size: var(--webchat-fontSizeBase300); line-height: var(--webchat-lineHeightBase300); @@ -86,7 +86,7 @@ } } - :global(.stacked-layout .webchat__bubble .webchat__fileContent__downloadIcon) { + :global(.stacked-layout .bubble .webchat__fileContent__downloadIcon) { color: var(--webchat-colorBrandForegroundLink); } /* #endregion */ @@ -378,7 +378,7 @@ display: none; } - :global(.part-grouping-decorator .webchat__bubble .webchat__bubble__content) { + :global(.part-grouping-decorator .bubble .bubble__content) { box-sizing: content-box; gap: var(--webchat-spacingVerticalXS); } @@ -416,7 +416,7 @@ margin-block-start: var(--webchat-part-grouping__message-status--space-block); } - :global(.stacked-layout .stacked-layout__content:has(.webchat__bubble)) { + :global(.stacked-layout .stacked-layout__content:has(.bubble)) { max-width: 100%; overflow: visible; } @@ -427,20 +427,30 @@ } /* #endregion */ + /* #region Carousel layout */ + :global(.carousel-layout) { + :global(.carousel-filmstrip__avatar-gutter), + :global(.carousel-filmstrip__alignment-pad) { + margin: 0; + width: var(--webchat-spacingHorizontalMNudge); + } + } + /* #endregion */ + /* #region Bubble surface */ - :global(.stacked-layout .webchat__bubble) { + :global(.stacked-layout .bubble) { max-width: min(var(--webchat-part-grouping__bubble--max-width), 100%); min-width: var(--webchat-part-grouping__bubble--min-width); overflow: visible; } - :global(.stacked-layout .webchat__bubble):has(:global(.activity-loader)) :global(.webchat__bubble__content) { + :global(.stacked-layout .bubble):has(:global(.activity-loader)) :global(.bubble__content) { background: unset; box-shadow: unset; border-color: transparent; } - :global(.stacked-layout .webchat__bubble .webchat__bubble__content) { + :global(.stacked-layout .bubble .bubble__content) { background-color: var(--webchat-part-grouping__bubble-content--background); border-color: var(--webchat-part-grouping__bubble--border-color); border-radius: var(--webchat-part-grouping__bubble--border-radius); @@ -452,7 +462,7 @@ min-height: var(--webchat-part-grouping__bubble--min-height); } - :global(.stacked-layout .webchat__bubble .webchat__text-content) { + :global(.stacked-layout .bubble .webchat__text-content) { font-size: var(--webchat__font-size--medium); line-height: var(--webchat__line-height--medium); min-height: auto; @@ -469,7 +479,7 @@ /* #endregion */ /* #region Code blocks */ - :global(.stacked-layout .webchat__bubble .code-block-content) { + :global(.stacked-layout .bubble .code-block-content) { border-radius: var(--webchat-borderRadiusXLarge); border: var(--webchat-strokeWidthThin) solid var(--webchat-colorNeutralStroke1); font-size: var(--webchat-fontSizeBase300); @@ -500,7 +510,7 @@ /* #endregion */ /* #region Collapsible content */ - :global(.stacked-layout .webchat__bubble .collapsible-content) { + :global(.stacked-layout .bubble .collapsible-content) { :global(.collapsible-content__summary) { &:focus-visible { border-radius: var(--webchat-borderRadiusSmall); @@ -586,7 +596,7 @@ } } - :global(.webchat__bubble) { + :global(.bubble) { position: static; } @@ -594,7 +604,7 @@ border-radius: var(--webchat-part-grouping__bubble--border-radius); } - :global(.webchat__bubble .webchat__bubble__content) { + :global(.bubble .bubble__content) { margin-block: calc(var(--webchat-spacingVerticalS) * -1); padding-block: var(--webchat-spacingVerticalS); } @@ -630,11 +640,18 @@ position: static; } - :global(.webchat__bubble) { + :global(.carousel-layout) { + :global(.carousel-filmstrip__avatar-gutter), + :global(.carousel-filmstrip__alignment-pad) { + width: 0; + } + } + + :global(.bubble) { position: static; width: var(--webchat-part-grouping__bubble--max-width); - :global(.webchat__bubble__content .adaptive-card-renderer > .ac-adaptiveCard) { + :global(.bubble__content .adaptive-card-renderer > .ac-adaptiveCard) { font-family: var(--webchat-fontFamilyBase); /* Remove double padding for adaptive cards inside bubble */ padding: unset !important; @@ -642,14 +659,14 @@ } &:not(.part-grouping-decorator--group) { - :global(.webchat__bubble__content) { + :global(.bubble__content) { display: flex; flex-direction: column; gap: var(--webchat-spacingVerticalS); } } - :global(.webchat__bubble .collapsible-content .collapsible-content__content) { + :global(.bubble .collapsible-content .collapsible-content__content) { margin-block: var(--webchat-spacingVerticalNone); :global(.stacked-layout__attachment-list) { @@ -689,7 +706,7 @@ } } - :global(.webchat__bubble .webchat__bubble__content) { + :global(.bubble .bubble__content) { margin-block: calc(var(--webchat-spacingVerticalS) * -1); padding-block: var(--webchat-spacingVerticalS); @@ -713,19 +730,18 @@ } } - &.part-grouping-decorator--from-bot:not(.part-grouping-decorator--group) :global(.webchat__bubble__content) { + &.part-grouping-decorator--from-bot:not(.part-grouping-decorator--group) :global(.bubble__content) { box-sizing: content-box; padding-block: var(--webchat-spacingVerticalM); padding-inline: var(--webchat-spacingHorizontalL); } - &:not(.part-grouping-decorator--group) :global(.stacked-layout .webchat__bubble .webchat__bubble__content) { + &:not(.part-grouping-decorator--group) :global(.stacked-layout .bubble .bubble__content) { border-width: var(--webchat-strokeWidthThin); border-style: solid; } - &:not(.part-grouping-decorator--group) - :global(.webchat__bubble:not(.webchat__bubble--from-user) .webchat__bubble__content) { + &:not(.part-grouping-decorator--group) :global(.bubble:not(.bubble--from-user) .bubble__content) { anchor-name: --webchat-flair; max-width: unset; } @@ -738,19 +754,19 @@ order: -1; } - &:has(:global(.webchat__bubble--from-user)) { - :global(.webchat__bubble) { + &:has(:global(.bubble--from-user)) { + :global(.bubble) { margin-block-end: var(--webchat-spacingVerticalM); } } - &:has(:global(.webchat__bubble.webchat__bubble--from-user)), + &:has(:global(.bubble.bubble--from-user)), &:has(:global(.pre-chat-message-activity)), &:has(:global(.liner-message-activity)) { padding-inline-start: var(--webchat-spacingHorizontalNone); } - :global(.webchat__bubble .webchat__text-content .webchat__text-content__generated-badge) { + :global(.bubble .webchat__text-content .webchat__text-content__generated-badge) { display: none; } diff --git a/packages/fluent-theme/src/components/theme/Theme.module.css b/packages/fluent-theme/src/components/theme/Theme.module.css index 4d8a2caf0c..805cf2f13b 100644 --- a/packages/fluent-theme/src/components/theme/Theme.module.css +++ b/packages/fluent-theme/src/components/theme/Theme.module.css @@ -324,12 +324,12 @@ } /* Transcript activity focused directly */ - > :global(.webchat__focus-trap > .webchat__basic-transcript__activity-body .webchat__bubble) { + > :global(.webchat__focus-trap > .webchat__basic-transcript__activity-body .bubble) { display: grid; grid-template-areas: 'focused-content'; position: static; - :global(.webchat__bubble__content) { + :global(.bubble__content) { grid-area: focused-content; } @@ -358,7 +358,7 @@ position: absolute; } - :global(.activity-decorator .webchat__bubble .webchat__bubble__nub-pad) { + :global(.activity-decorator .bubble .bubble__nub-pad) { display: none; } diff --git a/packages/test/page-object/src/globals/pageElements/activityContents.js b/packages/test/page-object/src/globals/pageElements/activityContents.js index 0b4f8e4b4a..bcdcf961cd 100644 --- a/packages/test/page-object/src/globals/pageElements/activityContents.js +++ b/packages/test/page-object/src/globals/pageElements/activityContents.js @@ -1,5 +1,5 @@ import activities from './activities'; export default function getActivityContents() { - return [].map.call(activities(), element => element.querySelector('.webchat__bubble__content')); + return [].map.call(activities(), element => element.querySelector('.bubble__content')); } diff --git a/packages/test/page-object/src/globals/pageElements/activityStatuses.js b/packages/test/page-object/src/globals/pageElements/activityStatuses.js index 00f6b8345e..fe98fd805b 100644 --- a/packages/test/page-object/src/globals/pageElements/activityStatuses.js +++ b/packages/test/page-object/src/globals/pageElements/activityStatuses.js @@ -2,6 +2,6 @@ import activities from './activities'; export default function getActivityStatuses() { return [].map.call(activities(), element => - element.querySelector('.webchat__carousel-filmstrip__status, .stacked-layout__status') + element.querySelector('.carousel-filmstrip__status, .stacked-layout__status') ); }