Skip to content

Commit 32a3b67

Browse files
committed
fluent: part grouping decorator and related adjustments
1 parent cba981a commit 32a3b67

File tree

7 files changed

+216
-46
lines changed

7 files changed

+216
-46
lines changed

packages/fluent-theme/src/components/activity/ActivityDecorator.module.css

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -63,12 +63,9 @@
6363
--webchat__bubble--max-width: 100%;
6464
--webchat__bubble--min-height: 20px;
6565

66-
display: flex;
67-
flex-flow: column nowrap;
68-
gap: var(--webchat-spacingVerticalS);
69-
margin-inline: var(--webchat-spacingHorizontalM);
7066
padding: var(--webchat-spacingVerticalMNudge) var(--webchat-spacingHorizontalM) var(--webchat-spacingVerticalM);
7167
position: relative;
68+
margin-inline: var(--webchat-spacingHorizontalNone);
7269

7370
:global(.stacked-layout) {
7471
margin: 0;
@@ -85,7 +82,7 @@
8582
flex-direction: column;
8683
gap: var(--webchat-spacingVerticalS);
8784
margin-block: calc(var(--webchat-spacingVerticalS) * -1);
88-
margin-inline: 20px calc(var(--webchat-spacingHorizontalS) * -1);
85+
margin-inline: calc(var(--webchat-spacingHorizontalS) * -1);
8986
padding-block: var(--webchat-spacingVerticalS);
9087
padding-inline: var(--webchat-spacingHorizontalS);
9188
}

packages/fluent-theme/src/components/activity/ActivityDecorator.tsx

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,14 @@
11
import { WebChatActivity } from 'botframework-webchat-component';
22
import cx from 'classnames';
33
import React, { ReactNode, memo } from 'react';
4-
import useVariants from '../../private/useVariants';
54
import { useStyles, useVariantClassName } from '../../styles';
65
import styles from './ActivityDecorator.module.css';
7-
import CopilotMessageHeader from './CopilotMessageHeader';
86

9-
function ActivityDecorator({ activity, children }: Readonly<{ activity: WebChatActivity; children: ReactNode }>) {
7+
function ActivityDecorator({ children }: Readonly<{ activity: WebChatActivity; children: ReactNode }>) {
108
const classNames = useStyles(styles);
11-
const variants = useVariants();
129
const variantClassName = useVariantClassName(styles);
1310

14-
const shouldRenderHeader = variants.includes('copilot') && activity?.from?.role === 'bot' && !!children;
15-
16-
return (
17-
<div className={cx(classNames['activity-decorator'], variantClassName)}>
18-
{shouldRenderHeader && <CopilotMessageHeader activity={activity} />}
19-
{children}
20-
</div>
21-
);
11+
return <div className={cx(classNames['activity-decorator'], variantClassName)}>{children}</div>;
2212
}
2313

2414
ActivityDecorator.displayName = 'ActivityDecorator';

packages/fluent-theme/src/components/activity/ActivityLoader.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,16 @@ import styles from './ActivityLoader.module.css';
88

99
function FluentActivityLoader({
1010
children,
11+
className,
1112
showLoader = true
12-
}: Readonly<{ children?: ReactNode | undefined; showLoader?: boolean }>) {
13+
}: Readonly<{ children?: ReactNode | undefined; className?: string | undefined; showLoader?: boolean }>) {
1314
const classNames = useStyles(styles);
1415
const variantClassName = useVariantClassName(classNames);
1516

1617
return (
1718
<Fragment>
1819
{children}
19-
{showLoader && <SlidingDots className={cx(classNames['activity-loader'], variantClassName)} />}
20+
{showLoader && <SlidingDots className={cx(classNames['activity-loader'], variantClassName, className)} />}
2021
</Fragment>
2122
);
2223
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { type WebChatActivity } from 'botframework-webchat-core';
2+
import { PartGrouping } from 'botframework-webchat-component/internal';
3+
import cx from 'classnames';
4+
import React, { memo } from 'react';
5+
6+
import CopilotMessageHeader from './CopilotMessageHeader';
7+
import useVariants from '../../private/useVariants';
8+
import { useStyles, useVariantClassName } from '../../styles';
9+
10+
import styles from './PartGroupingingDecorator.module.css';
11+
12+
type PartGroupingDecoratorProps = Readonly<{
13+
activities: readonly WebChatActivity[];
14+
children?: React.ReactNode;
15+
}>;
16+
17+
function PartGroupingDecorator(props: PartGroupingDecoratorProps) {
18+
const {
19+
activities: [activity]
20+
} = props;
21+
const variants = useVariants();
22+
const classNames = useStyles(styles);
23+
const variantClassName = useVariantClassName(styles);
24+
25+
const shouldRenderHeader = variants.includes('copilot') && activity?.from?.role === 'bot';
26+
27+
return (
28+
<div className={cx(classNames['part-grouping-decorator'], variantClassName)}>
29+
{shouldRenderHeader && <CopilotMessageHeader activity={activity} />}
30+
<PartGrouping {...props} />
31+
</div>
32+
);
33+
}
34+
35+
export default memo(PartGroupingDecorator);
36+
export { type PartGroupingDecoratorProps };
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
:global(.webchat-fluent) .part-grouping-decorator {
2+
display: grid;
3+
font-family: var(--webchat-fontFamilyBase);
4+
gap: var(--webchat-spacingVerticalS);
5+
margin-inline: var(--webchat-spacingHorizontalM) var(--webchat-spacingHorizontalXXL);
6+
7+
:global(.stacked-layout) {
8+
margin-inline: var(--webchat-spacingHorizontalNone);
9+
}
10+
11+
:global(.stacked-layout .collapsible-grouping__header) {
12+
padding: var(--webchat-spacingVerticalNone) var(--webchat-spacingHorizontalNone);
13+
position: relative;
14+
15+
:global(.webchat__activity-button) {
16+
background: transparent;
17+
border: none;
18+
cursor: pointer;
19+
font-size: var(--webchat-fontSizeBase300);
20+
padding: var(--webchat-spacingVerticalXXS) var(--webchat-spacingHorizontalXS);
21+
22+
&::after {
23+
content: '';
24+
cursor: pointer;
25+
display: block;
26+
inset: 0;
27+
position: absolute;
28+
}
29+
}
30+
}
31+
32+
:global(.stacked-layout .collapsible-grouping__content) {
33+
gap: var(--webchat-spacingVerticalS);
34+
padding: var(--webchat-spacingVerticalS) var(--webchat-spacingHorizontalL);
35+
}
36+
37+
:global(.stacked-layout .collapsible-grouping) {
38+
display: grid;
39+
gap: var(--webchat-spacingVerticalM);
40+
}
41+
42+
:global(.stacked-layout .stacked-layout__title),
43+
:global(.collapsible-grouping__title) {
44+
color: var(--webchat-colorNeutralForeground2);
45+
font-size: var(--webchat-fontSizeBase300);
46+
font-weight: var(--webchat-fontWeightSemibold);
47+
line-height: var(--webchat-lineHeightBase300);
48+
}
49+
50+
:global(.stacked-layout .stacked-layout__title .collapsible-content__summary-text) {
51+
flex: none;
52+
}
53+
54+
:global(.collapsible-grouping) {
55+
border: none;
56+
max-width: 100%;
57+
58+
:global(.collapsible-grouping__header) {
59+
border: none;
60+
}
61+
62+
:global(.stacked-layout__message-status) {
63+
color: var(--webchat-colorNeutralForeground2);
64+
font-size: var(--webchat-fontSizeBase400);
65+
margin-block: var(--webchat__bubble--block-padding);
66+
margin-inline-start: var(--webchat__bubble--block-padding);
67+
68+
:global(.component-icon) {
69+
height: 19px;
70+
width: 19px;
71+
}
72+
}
73+
74+
:global(.stacked-layout .activity-loader) {
75+
display: none;
76+
}
77+
78+
:global(.activity-decorator .webchat__bubble .webchat__bubble__content) {
79+
gap: var(--webchat-spacingVerticalXS);
80+
box-sizing: content-box;
81+
}
82+
}
83+
84+
&.variant-fluent {
85+
:global(.stacked-layout .stacked-layout__message-status) {
86+
margin-inline-end: calc(var(--webchat-spacingHorizontalS) * -1);
87+
}
88+
}
89+
90+
&.variant-copilot {
91+
:global(.collapsible-grouping__content) {
92+
anchor-name: --webchat-flair;
93+
background: var(--webchat-colorNeutralBackground1);
94+
border-radius: var(--webchat-borderRadius2XLarge);
95+
margin: calc(var(--webchat-spacingVerticalMNudge) * -1) var(--webchat-spacingHorizontalNone);
96+
97+
&:global(.collapsible-grouping__content--open) {
98+
margin: var(--webchat-spacingVerticalNone) var(--webchat-spacingHorizontalNone);
99+
}
100+
}
101+
102+
:global(.stacked-layout .stacked-layout__message-status) {
103+
margin-inline-end: var(--webchat-spacingHorizontalS);
104+
}
105+
106+
:global(.collapsible-grouping__content .border-flair) {
107+
border-radius: var(--webchat-borderRadius2XLarge);
108+
inset: anchor(top) anchor(right) anchor(bottom) anchor(left);
109+
position-anchor: --webchat-flair;
110+
}
111+
112+
:global(.webchat__bubble:not(.webchat__bubble--from-user)::after) {
113+
outline-offset: 4px;
114+
}
115+
}
116+
}

packages/fluent-theme/src/components/theme/Theme.module.css

Lines changed: 49 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,7 @@
165165
--webchat-borderRadiusMedium: var(--borderRadiusMedium, 4px);
166166
--webchat-borderRadiusLarge: var(--borderRadiusLarge, 6px);
167167
--webchat-borderRadiusXLarge: var(--borderRadiusXLarge, 8px);
168+
--webchat-borderRadius2XLarge: var(--borderRadius2XLarge, 12px);
168169

169170
/* https://github.com/microsoft/fluentui/blob/master/packages/tokens/src/global/strokeWidths.ts */
170171
--webchat-strokeWidthThin: var(--strokeWidthThin, 1px);
@@ -283,42 +284,70 @@
283284

284285
/* Transcript focus indicator color when in focus-visible state */
285286
:global(.webchat-fluent).theme
286-
:global(
287-
.webchat__basic-transcript
288-
.webchat__basic-transcript__terminator:focus-visible
289-
+ .webchat__basic-transcript__focus-indicator
290-
) {
287+
:global(.transcript-focus-area .transcript-focus-area__terminator + .transcript-focus-area__transcript-indicator) {
291288
border-color: var(--webchat-colorStrokeFocus2);
292289
}
293290

294-
/* Transcript Activity indicator without focus-visible state */
295-
:global(.webchat-fluent).theme
296-
:global(.webchat__basic-transcript:not(:focus-visible) .webchat__basic-transcript__activity-indicator--focus) {
291+
/* Transcript Content indicator without focus-visible state */
292+
:global(.webchat-fluent).theme :global(.transcript-focus-area:not(:focus-visible) .transcript-focus-area__indicator) {
297293
/* Hide activity focus when no focus-visible state */
298294
display: none;
299295
}
300296

301-
/* Transcript Focus indicator when activity focused */
297+
/* Transcript Focus indicator when content focused */
302298
:global(.webchat-fluent).theme
303-
:global(.webchat__basic-transcript:focus-visible:has(.webchat__basic-transcript__activity-indicator--focus)) {
304-
:global(.webchat__basic-transcript__focus-indicator) {
299+
:global(.transcript-focus-area:focus-visible:has(.transcript-focus-area__content--focused)) {
300+
:global(.transcript-focus-area__transcript-indicator) {
305301
/* Hide transcript focus when an activity is focused */
306302
display: none;
307303
}
308304
}
309305

310-
/* Transcript focused activity */
311-
:global(.webchat-fluent).theme
312-
:global(
313-
.webchat__basic-transcript:focus-visible
314-
.webchat__basic-transcript__activity:has(.webchat__basic-transcript__activity-indicator--focus)
315-
) {
316-
:global(.webchat__basic-transcript__activity-indicator--focus) {
317-
/* Hide transcript activity focus when the activity is focused */
306+
/* Transcript focused content */
307+
:global(.webchat-fluent).theme :global(.transcript-focus-area:focus-visible .transcript-focus-area__content--focused) {
308+
:global(.transcript-focus-area__indicator) {
309+
/* Hide transcript activity focus when the content is focused */
310+
display: none;
311+
}
312+
313+
/* Transcript ctivity focused directly */
314+
> :global(.transcript-focus-area__content-root > .webchat__focus-trap .webchat__bubble) {
315+
display: grid;
316+
grid-template-areas: 'focused-content';
317+
position: static;
318+
319+
:global(.webchat__bubble__content) {
320+
grid-area: focused-content;
321+
}
322+
323+
&::after {
324+
border-radius: var(--webchat__bubble--border-radius);
325+
content: '';
326+
grid-area: focused-content;
327+
height: 100%;
328+
outline: var(--webchat-strokeWidthThick) solid var(--webchat-colorStrokeFocus2);
329+
pointer-events: none;
330+
width: 100%;
331+
}
332+
}
333+
334+
&:has(:global(.collapsible-grouping)) > :global(.transcript-focus-area__indicator) {
335+
border-radius: var(--webchat-borderRadiusXLarge);
336+
border: none;
337+
box-sizing: content-box;
338+
display: block;
339+
height: calc(100% - var(--webchat-spacingVerticalXS));
340+
margin: 0;
341+
margin: var(--webchat-spacingVerticalXXS) calc(var(--webchat-spacingHorizontalSNudge) * -1);
342+
outline: var(--webchat-strokeWidthThick) solid var(--webchat-colorStrokeFocus2);
343+
padding: var(--webchat-spacingVerticalNone) var(--webchat-spacingHorizontalSNudge);
344+
width: 100%;
345+
}
346+
347+
:global(.activity-decorator .webchat__bubble .webchat__bubble__nub-pad) {
318348
display: none;
319349
}
320350

321-
:global(.webchat__bubble)::after,
322351
:global(.pre-chat-message-activity)::after {
323352
border-radius: var(--webchat__bubble--border-radius);
324353
content: '';
@@ -596,13 +625,6 @@
596625
}
597626
}
598627

599-
/* Monochrome image masker */
600-
:global(.webchat-fluent).theme :global(.webchat__monochrome-image-masker) {
601-
background-color: currentColor;
602-
height: 1em;
603-
width: 1em;
604-
}
605-
606628
/* Feedback button */
607629
:global(.webchat-fluent).theme :global(.webchat__thumb-button) {
608630
:global(.webchat__thumb-button__image) {

packages/fluent-theme/src/private/FluentThemeProvider.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import { type ActivityMiddleware, type StyleOptions, type TypingIndicatorMiddleware } from 'botframework-webchat-api';
33
import {
44
createActivityBorderMiddleware,
5+
createActivityGroupingMiddleware,
56
DecoratorComposer,
67
type DecoratorMiddleware
78
} from 'botframework-webchat-api/decorator';
@@ -20,6 +21,7 @@ import { WebChatTheme } from '../components/theme';
2021
import SlidingDotsTypingIndicator from '../components/typingIndicator/SlidingDotsTypingIndicator';
2122
import { createStyles } from '../styles';
2223
import VariantComposer, { VariantList } from './VariantComposer';
24+
import PartGroupDecorator from '../components/activity/PartGroupingDecorator';
2325

2426
const { ThemeProvider } = Components;
2527

@@ -54,6 +56,12 @@ const activityMiddleware: readonly ActivityMiddleware[] = Object.freeze([
5456
const sendBoxMiddleware = [() => () => () => PrimarySendBox];
5557

5658
const decoratorMiddleware: readonly DecoratorMiddleware[] = Object.freeze([
59+
createActivityGroupingMiddleware(next => request => {
60+
if (request.groupingName === 'part') {
61+
return PartGroupDecorator;
62+
}
63+
return next(request);
64+
}),
5765
createActivityBorderMiddleware(function FluentBorderLoader({ request, Next, ...props }) {
5866
return (
5967
<ActivityLoader showLoader={props.showLoader ?? request.livestreamingState === 'preparing'}>

0 commit comments

Comments
 (0)