Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 [`[email protected]`](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 [`[email protected]`](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

Expand Down
2 changes: 1 addition & 1 deletion __tests__/html2/accessibility/attachment/aria.html
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
await pageConditions.scrollToBottomCompleted();

const carouselContainer = document
.querySelector('.webchat__carousel-filmstrip')
.querySelector('.carousel-filmstrip')
.getAttribute('aria-labelledby');

expect(carouselContainer).toBeFalsy();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
4 changes: 2 additions & 2 deletions __tests__/html2/carousel/flipperButton.html
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
4 changes: 2 additions & 2 deletions __tests__/html2/carousel/flipperButton.rtl.html
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
4 changes: 2 additions & 2 deletions __tests__/html2/carousel/navigation.tab.html
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
);
await pageConditions.stabilized(
'carousel',
() => document.querySelector('.webchat__carousel-filmstrip').scrollLeft,
() => document.querySelector('.carousel-filmstrip').scrollLeft,
5,
5000
);
Expand All @@ -70,7 +70,7 @@
);
await pageConditions.stabilized(
'carousel',
() => document.querySelector('.webchat__carousel-filmstrip').scrollLeft,
() => document.querySelector('.carousel-filmstrip').scrollLeft,
5,
5000
);
Expand Down
4 changes: 2 additions & 2 deletions __tests__/html2/fluentTheme/carousel.html
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Binary file modified __tests__/html2/fluentTheme/carousel.html.snap-1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified __tests__/html2/fluentTheme/carousel.html.snap-2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified __tests__/html2/grouping/fluentTheme.html.snap-1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion __tests__/html2/middleware/cardAction/signIn.html
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
131 changes: 131 additions & 0 deletions packages/component/src/Activity/Bubble.module.css
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
}
56 changes: 17 additions & 39 deletions packages/component/src/Activity/Bubble.tsx
Original file line number Diff line number Diff line change
@@ -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;
}
Expand All @@ -51,13 +34,13 @@ function acuteNubSVG(nubSize, strokeWidth, side, upSideDown = false) {

return (
<svg
className="webchat__bubble__nub"
className={classNames['bubble__nub']}
version="1.1"
viewBox={`0 0 ${nubSize} ${nubSize}`}
xmlns="http://www.w3.org/2000/svg"
>
<g transform={`${horizontalTransform} ${verticalTransform}`}>
<path className="webchat__bubble__nub-outline" d={`M${p1} L${p2} L${p3}`} />
<path className={classNames['bubble__nub-outline']} d={`M${p1} L${p2} L${p3}`} />
</g>
</svg>
);
Expand Down Expand Up @@ -85,8 +68,6 @@ function Bubble(props: BubbleProps) {
nub = false
} = validateProps(bubblePropsSchema, props);

const [{ bubble: bubbleStyleSet }] = useStyleSet();
const [direction] = useDirection();
const [
{
bubbleBorderWidth,
Expand All @@ -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
? {
Expand All @@ -116,23 +97,20 @@ function Bubble(props: BubbleProps) {
return (
<div
aria-hidden={ariaHidden}
className={classNames(
'webchat__bubble',
className={cx(
classNames.bubble,
{
'webchat__bubble--from-user': fromUser,
'webchat__bubble--hide-nub': nub !== true && nub !== false,
'webchat__bubble--nub-on-top': isZeroOrPositive(nubOffset),
'webchat__bubble--rtl': direction === 'rtl',
'webchat__bubble--show-nub': nub === true
[classNames['bubble--from-user']]: fromUser,
[classNames['bubble--hide-nub']]: nub !== true && nub !== false,
[classNames['bubble--nub-on-top']]: isZeroOrPositive(nubOffset),
[classNames['bubble--show-nub']]: nub === true
},
rootClassName,
bubbleStyleSet + '',
className
)}
>
<div className="webchat__bubble__nub-pad" />
<div className="webchat__bubble__content">{children}</div>
{nub === true && acuteNubSVG(nubSize, borderWidth, side, !isZeroOrPositive(nubOffset))}
<div className={classNames['bubble__nub-pad']} />
<div className={classNames['bubble__content']}>{children}</div>
{nub === true && acuteNubSVG(nubSize, borderWidth, side, !isZeroOrPositive(nubOffset), classNames)}
</div>
);
}
Expand Down
Loading
Loading