From cfd487947c33c3b4c660805a91f2438c225a16b0 Mon Sep 17 00:00:00 2001 From: Eugene Olonov Date: Fri, 30 Oct 2026 09:47:31 -0700 Subject: [PATCH 01/24] Fluent: style feedback buttons and form through the CSS module theme FluentThemeProvider wraps everything in with Fluent's class names for the Web Chat local names it styles, so Fluent rules select the feedback button and form by local name instead of :global() readable class names. fluentCSSModuleOverrides.spec.ts checks the lists against the CSS. --- .../src/components/theme/Theme.module.css | 24 ++--- .../src/private/FluentThemeProvider.tsx | 52 +++++----- .../private/fluentCSSModuleOverrides.spec.ts | 96 +++++++++++++++++++ .../src/private/fluentCSSModuleOverrides.ts | 21 ++++ .../src/private/fluentCSSModuleTheme.ts | 11 +++ .../src/private/pickCSSModule.spec.ts | 35 +++++++ .../fluent-theme/src/private/pickCSSModule.ts | 20 ++++ 7 files changed, 223 insertions(+), 36 deletions(-) create mode 100644 packages/fluent-theme/src/private/fluentCSSModuleOverrides.spec.ts create mode 100644 packages/fluent-theme/src/private/fluentCSSModuleOverrides.ts create mode 100644 packages/fluent-theme/src/private/fluentCSSModuleTheme.ts create mode 100644 packages/fluent-theme/src/private/pickCSSModule.spec.ts create mode 100644 packages/fluent-theme/src/private/pickCSSModule.ts diff --git a/packages/fluent-theme/src/components/theme/Theme.module.css b/packages/fluent-theme/src/components/theme/Theme.module.css index 180985645f..17439787da 100644 --- a/packages/fluent-theme/src/components/theme/Theme.module.css +++ b/packages/fluent-theme/src/components/theme/Theme.module.css @@ -667,17 +667,17 @@ } /* Feedback button */ -:global(.webchat-fluent).theme :global(.thumb-button), -:global(.webchat-fluent).theme :global(.thumb-button.thumb-button--large) { - :global(.thumb-button__image) { +:global(.webchat-fluent).theme .thumb-button, +:global(.webchat-fluent).theme .thumb-button.thumb-button--large { + .thumb-button__image { color: var(--webchat-colorNeutralForeground1); } - &:has(:global(.thumb-button__input):focus-visible) { + &:has(.thumb-button__input:focus-visible) { outline: var(--webchat-strokeWidthThick) solid var(--webchat-colorStrokeFocus2); } - &:has(:global(.thumb-button__input)[aria-disabled='true']) :global(.thumb-button__image) { + &:has(.thumb-button__input[aria-disabled='true']) .thumb-button__image { color: var(--webchat-colorNeutralForegroundDisabled); } } @@ -698,26 +698,26 @@ } /* Feedback Form */ -:global(.webchat-fluent).theme :global(.feedback-form) { - :global(.feedback-form__form-header) { +:global(.webchat-fluent).theme .feedback-form { + .feedback-form__form-header { color: var(--webchat-colorNeutralForeground1); font-size: var(--webchat-fontSizeBase300); font-weight: var(--webchat-fontWeightRegular); line-height: var(--webchat-lineHeightBase300); } - :global(.feedback-form__form) { + .feedback-form__form { gap: var(--webchat-spacingVerticalXS); } - :global(.feedback-form__form-footer) { + .feedback-form__form-footer { color: var(--webchat-colorNeutralForeground1); font-size: var(--webchat-fontSizeBase100); font-weight: var(--webchat-fontWeightRegular); line-height: var(--webchat-lineHeightBase200); } - :global(.feedback-form__submit-button) { + .feedback-form__submit-button { background-color: var(--webchat-colorBrandBackground); border: var(--webchat-strokeWidthThin) solid var(--webchat-colorBrandBackground); border-radius: var(--webchat-borderRadiusMedium); @@ -743,7 +743,7 @@ } } - :global(.feedback-form__cancel-button) { + .feedback-form__cancel-button { background-color: var(--webchat-colorNeutralBackground1); border: var(--webchat-strokeWidthThin) solid var(--webchat-colorNeutralStroke1); border-radius: var(--webchat-borderRadiusMedium); @@ -770,7 +770,7 @@ } } - :global(.feedback-form__text-box) { + .feedback-form__text-box { background-color: var(--webchat-colorNeutralBackground1); border-radius: var(--webchat-borderRadiusMedium); border: var(--webchat-strokeWidthThin) solid var(--webchat-colorNeutralStroke1); diff --git a/packages/fluent-theme/src/private/FluentThemeProvider.tsx b/packages/fluent-theme/src/private/FluentThemeProvider.tsx index 2e30b70ab9..e9e2f2d5e6 100644 --- a/packages/fluent-theme/src/private/FluentThemeProvider.tsx +++ b/packages/fluent-theme/src/private/FluentThemeProvider.tsx @@ -9,6 +9,7 @@ import { type DecoratorMiddleware } from 'botframework-webchat/decorator.js'; import { + CSSModuleThemeComposer, isVoiceTranscriptActivity, type ActivityMiddleware, type ActivityStatusMiddleware, @@ -28,6 +29,7 @@ import { TelephoneKeypadProvider } from '../components/telephoneKeypad'; import { WebChatTheme } from '../components/theme'; import SlidingDotsTypingIndicator from '../components/typingIndicator/SlidingDotsTypingIndicator'; import FluentThemeStylesheet from '../stylesheet/FluentThemeStylesheet'; +import fluentCSSModuleTheme from './fluentCSSModuleTheme'; import VariantComposer, { variantNameSchema } from './VariantComposer'; const fluentThemeProviderPropsSchema = pipe( @@ -112,30 +114,32 @@ function FluentThemeProvider(props: FluentThemeProviderProps) { ); return ( - - - - - - - {/* - is not set up yet, we have no place to send nonce. - This is temporal, until we decided to fold back into . - */} - - {children} - - - - - - + + + + + + + + {/* + is not set up yet, we have no place to send nonce. + This is temporal, until we decided to fold back into . + */} + + {children} + + + + + + + ); } diff --git a/packages/fluent-theme/src/private/fluentCSSModuleOverrides.spec.ts b/packages/fluent-theme/src/private/fluentCSSModuleOverrides.spec.ts new file mode 100644 index 0000000000..5814c7d9b9 --- /dev/null +++ b/packages/fluent-theme/src/private/fluentCSSModuleOverrides.spec.ts @@ -0,0 +1,96 @@ +/// + +import * as JestGlobals from '@jest/globals'; +import { scenario } from '@testduet/given-when-then'; +import { readdirSync, readFileSync } from 'fs'; +import { transform } from 'lightningcss'; +import { join } from 'path'; + +import { themeOverrides } from './fluentCSSModuleOverrides'; + +const PACKAGES_PATH = join(__dirname, '../../..'); + +const listCSSModules = (packageName: string): readonly string[] => + // Paths are constants of this test, not user input. + // eslint-disable-next-line security/detect-non-literal-fs-filename + readdirSync(join(PACKAGES_PATH, packageName, 'src'), { recursive: true, encoding: 'utf8' }) + .filter(path => path.endsWith('.module.css') && !path.includes('__tests__')) + .map(path => join(PACKAGES_PATH, packageName, 'src', path)); + +// Local names as Lightning CSS (the CSS module compiler of the build) exports them. +const getLocalNames = (path: string): readonly string[] => + Object.keys( + // eslint-disable-next-line security/detect-non-literal-fs-filename + transform({ code: readFileSync(path), cssModules: true, filename: path }).exports ?? {} + ); + +// Class names selected through `:global(…)`, which only match readable class names. +function getGlobalClassNames(path: string): readonly string[] { + // eslint-disable-next-line security/detect-non-literal-fs-filename + const css = readFileSync(path, 'utf8'); + const classNames: string[] = []; + + for (let index = css.indexOf(':global('); ~index; index = css.indexOf(':global(', index + 1)) { + let depth = 0; + let end = index + ':global'.length; + + for (; end < css.length; end++) { + if (css[+end] === '(') { + depth++; + } else if (css[+end] === ')' && !--depth) { + break; + } + } + + // The whole match is ".". + classNames.push(...Array.from(css.slice(index, end).matchAll(/\.-?[_a-z][-\w]*/giu), ([match]) => match.slice(1))); + } + + return Object.freeze(classNames); +} + +const webChatLocalNames = new Set([...listCSSModules('component'), ...listCSSModules('bundle')].flatMap(getLocalNames)); + +const OVERRIDES_BY_FLUENT_CSS_MODULE: Readonly> = Object.freeze({ + 'fluent-theme/src/components/theme/Theme.module.css': themeOverrides +}); + +scenario( + 'Fluent overrides of Web Chat CSS modules', + bdd => { + bdd + .given('the local names that FluentThemeProvider passes through the CSS module theme', () => + Object.entries(OVERRIDES_BY_FLUENT_CSS_MODULE).map(([path, localNames]) => ({ + localNames, + path: join(PACKAGES_PATH, path) + })) + ) + .when('reading the Fluent CSS modules', overrides => + overrides.map(({ localNames, path }) => ({ + fluentLocalNames: getLocalNames(path), + globalClassNames: getGlobalClassNames(path), + localNames, + path + })) + ) + .then('every local name should be defined by the Fluent CSS module', (_, results) => + results.forEach(({ fluentLocalNames, localNames }) => + expect(fluentLocalNames).toEqual(expect.arrayContaining([...localNames])) + ) + ) + .and('every local name should be defined by a Web Chat CSS module', (_, results) => + results.forEach(({ localNames }) => + expect(localNames.filter(localName => !webChatLocalNames.has(localName))).toEqual([]) + ) + ) + .and('no local name should still be selected through :global()', (_, results) => + results.forEach(({ globalClassNames, localNames }) => + expect(localNames.filter(localName => globalClassNames.includes(localName))).toEqual([]) + ) + ) + .and('the lists should be sorted', (_, results) => + results.forEach(({ localNames }) => expect(localNames).toEqual([...localNames].sort())) + ); + }, + JestGlobals +); diff --git a/packages/fluent-theme/src/private/fluentCSSModuleOverrides.ts b/packages/fluent-theme/src/private/fluentCSSModuleOverrides.ts new file mode 100644 index 0000000000..822fe29fdc --- /dev/null +++ b/packages/fluent-theme/src/private/fluentCSSModuleOverrides.ts @@ -0,0 +1,21 @@ +// Local names of Web Chat CSS modules that each Fluent CSS module styles, kept sorted. +// FluentThemeProvider passes Fluent's class names for them to Web Chat's useStyles() through , +// so Fluent rules select them as local names (`.thumb-button`), not as readable class names (`:global(.thumb-button)`). +// fluentCSSModuleOverrides.spec.js checks these lists against the CSS. + +const themeOverrides: readonly string[] = Object.freeze([ + // Feedback + 'feedback-form', + 'feedback-form__cancel-button', + 'feedback-form__form', + 'feedback-form__form-footer', + 'feedback-form__form-header', + 'feedback-form__submit-button', + 'feedback-form__text-box', + 'thumb-button', + 'thumb-button--large', + 'thumb-button__image', + 'thumb-button__input' +]); + +export { themeOverrides }; diff --git a/packages/fluent-theme/src/private/fluentCSSModuleTheme.ts b/packages/fluent-theme/src/private/fluentCSSModuleTheme.ts new file mode 100644 index 0000000000..2b64a19e83 --- /dev/null +++ b/packages/fluent-theme/src/private/fluentCSSModuleTheme.ts @@ -0,0 +1,11 @@ +import { combineCSSModules } from 'botframework-webchat/internal.js'; + +import themeStyles from '../components/theme/Theme.module.css'; +import { themeOverrides } from './fluentCSSModuleOverrides'; +import pickCSSModule from './pickCSSModule'; + +// Fluent's class names for the Web Chat elements it styles, for in FluentThemeProvider. +// Built once, so every useStyles() below the composer gets a stable theme. +const fluentCSSModuleTheme = combineCSSModules(pickCSSModule(themeStyles, themeOverrides)); + +export default fluentCSSModuleTheme; diff --git a/packages/fluent-theme/src/private/pickCSSModule.spec.ts b/packages/fluent-theme/src/private/pickCSSModule.spec.ts new file mode 100644 index 0000000000..485774df72 --- /dev/null +++ b/packages/fluent-theme/src/private/pickCSSModule.spec.ts @@ -0,0 +1,35 @@ +/// + +import * as JestGlobals from '@jest/globals'; +import { scenario } from '@testduet/given-when-then'; + +import pickCSSModule from './pickCSSModule'; + +scenario( + 'pickCSSModule', + bdd => { + bdd + .given('a CSS module with three local names', () => + Object.freeze({ + bubble: 'wA_bubble', + bubble__content: 'wA_bubble__content', + 'part-grouping-decorator': 'wA_part-grouping-decorator' + }) + ) + .when('picking two of them', cssModule => pickCSSModule(cssModule, ['bubble', 'bubble__content'])) + .then('should return the class names of the picked local names only', (_, result) => + expect(result).toStrictEqual({ bubble: 'wA_bubble', bubble__content: 'wA_bubble__content' }) + ) + .and('the result should be frozen', (_, result) => expect(Object.isFrozen(result)).toBe(true)); + + bdd + .given('a CSS module without "bubble"', () => + Object.freeze({ 'part-grouping-decorator': 'wA_part-grouping-decorator' }) + ) + .when('picking "bubble"', cssModule => () => pickCSSModule(cssModule, ['bubble'])) + .then('should throw', (_, pick) => + expect(pick).toThrow('The CSS module does not define the local name "bubble".') + ); + }, + JestGlobals +); diff --git a/packages/fluent-theme/src/private/pickCSSModule.ts b/packages/fluent-theme/src/private/pickCSSModule.ts new file mode 100644 index 0000000000..180406ce41 --- /dev/null +++ b/packages/fluent-theme/src/private/pickCSSModule.ts @@ -0,0 +1,20 @@ +import { parse, string } from 'valibot'; + +/** + * Picks the class names of the given local names from a CSS module, for `CSSModuleThemeComposer`. + * + * Throws if the CSS module does not define one of the local names. + */ +export default function pickCSSModule( + cssModule: CSSModuleClasses, + localNames: readonly string[] +): Readonly> { + return Object.freeze( + Object.fromEntries( + localNames.map(localName => [ + localName, + parse(string(`The CSS module does not define the local name "${localName}".`), cssModule[`${localName}`]) + ]) + ) + ); +} From 4379c147990af79e6881723c658b089f97a47745 Mon Sep 17 00:00:00 2001 From: Eugene Olonov Date: Fri, 30 Oct 2026 09:48:07 -0700 Subject: [PATCH 02/24] Fluent: style code blocks and the view code dialog through the CSS module theme --- .../activity/PartGroupingDecorator.module.css | 8 ++--- .../src/components/theme/Theme.module.css | 32 +++++++++---------- .../private/fluentCSSModuleOverrides.spec.ts | 3 +- .../src/private/fluentCSSModuleOverrides.ts | 28 +++++++++++++--- .../src/private/fluentCSSModuleTheme.ts | 8 +++-- 5 files changed, 52 insertions(+), 27 deletions(-) diff --git a/packages/fluent-theme/src/components/activity/PartGroupingDecorator.module.css b/packages/fluent-theme/src/components/activity/PartGroupingDecorator.module.css index 5c090cbc94..8083bb68a6 100644 --- a/packages/fluent-theme/src/components/activity/PartGroupingDecorator.module.css +++ b/packages/fluent-theme/src/components/activity/PartGroupingDecorator.module.css @@ -505,7 +505,7 @@ /* #endregion */ /* #region Code blocks */ - :global(.stacked-layout .bubble .code-block-content) { + :global(.stacked-layout) :global(.bubble) .code-block-content { border-radius: var(--webchat-borderRadiusXLarge); border: var(--webchat-strokeWidthThin) solid var(--webchat-colorNeutralStroke1); font-size: var(--webchat-fontSizeBase300); @@ -513,16 +513,16 @@ margin-block: var(--webchat-spacingVerticalNone); margin-inline: var(--webchat-part-grouping__code-block--space-inline); - :global(.code-block-content__header) { + .code-block-content__header { padding: var(--webchat-spacingVerticalM) var(--webchat-spacingHorizontalL); } - :global(.code-block-content__code-block) { + .code-block-content__code-block { padding-block: var(--webchat-spacingVerticalM); padding-inline: var(--webchat-spacingHorizontalL) var(--webchat-spacingHorizontalS); } - :global(.code-block-copy-button) { + .code-block-copy-button { --webchat__code-block__copy-button--color: var(--webchat-colorNeutralForeground1); --webchat__code-block__copy-button--background: var(--webchat-colorNeutralBackground3); diff --git a/packages/fluent-theme/src/components/theme/Theme.module.css b/packages/fluent-theme/src/components/theme/Theme.module.css index 17439787da..4d929967b3 100644 --- a/packages/fluent-theme/src/components/theme/Theme.module.css +++ b/packages/fluent-theme/src/components/theme/Theme.module.css @@ -234,12 +234,12 @@ } /* Scrollbars */ -:global(.webchat-fluent).theme :global(.code-block-content .code-block-content__code-block), +:global(.webchat-fluent).theme .code-block-content .code-block-content__code-block, :global(.webchat-fluent).theme :global(.send-box-attachment-bar.send-box-attachment-bar--as-list-item), :global(.webchat-fluent).theme :global(.text-area.text-area--scroll), :global(.webchat-fluent).theme :global(.webchat__basic-transcript .webchat__basic-transcript__scrollable), :global(.webchat-fluent).theme :global(.render-markdown [data-math-type='block']), -:global(.webchat-fluent).theme :global(.view-code-dialog__body), +:global(.webchat-fluent).theme .view-code-dialog__body, :global(.webchat-fluent).theme :global(.view-code-dialog__code-body) { /* Edge uses -webkit-scrollbar if scrollbar-* is not set */ scrollbar-color: unset; @@ -452,7 +452,7 @@ } /* Code block */ -:global(.webchat-fluent).theme :global(.code-block) { +:global(.webchat-fluent).theme .code-block { border: none; font-family: var(--webchat-fontFamilyMonospace); font-size: var(--webchat-fontSizeBase300); @@ -460,7 +460,7 @@ --webchat__code-block__copy-button--color: var(--webchat-colorNeutralForeground1); --webchat__code-block__copy-button--background: var(--webchat-colorNeutralBackground3); - :global(.code-block-copy-button) { + .code-block-copy-button { background: transparent; border: none; color: var(--webchat__code-block__copy-button--color); @@ -475,7 +475,7 @@ border: var(--webchat-strokeWidthThin) solid ButtonText; } - :global(.code-block-copy-button__icon) { + .code-block-copy-button__icon { --webchat__component-icon--color: currentColor; @media (forced-colors: active) { @@ -499,21 +499,21 @@ } } - &:global(:has(.code-block__body:focus-visible)):focus-within { + &:has(.code-block__body:focus-visible):focus-within { outline: var(--webchat-strokeWidthThick) solid var(--webchat-colorStrokeFocus2); outline-offset: calc(var(--webchat-strokeWidthThick) * -1); - :global(.code-block__body) { + .code-block__body { outline: none; } } - :global(.code-block__body) { + .code-block__body { /* TODO: what is the right value? The value below matches "normal" for contents display */ line-height: 1.435; } - &:global(:has(> .code-block__theme--github-dark-default)) { + &:has(> .code-block__theme--github-dark-default) { --webchat__background--code-block: var(--codeBlockBackground, var(--webchat-colorGrey8)); --webchat__code-block__copy-button--background: var( --codeBlockCopyButtonBackgroundPressed, @@ -523,7 +523,7 @@ --webchat__color--code-block: var(--codeBlockForeground, var(--webchat-colorGrey98)); } - &:global(:has(> .code-block__theme--github-light-default)) { + &:has(> .code-block__theme--github-light-default) { --webchat__background--code-block: var(--codeBlockBackground, var(--webchat-colorGrey98)); --webchat__code-block__copy-button--background: var(--codeBlockBackgroundPressed, var(--webchat-colorGrey92)); --webchat__code-block__copy-button--color: var(--codeBlockCopyButtonForeground, var(--webchat__color--code-block)); @@ -532,7 +532,7 @@ } /* View Code dialog */ -:global(.webchat-fluent).theme :global(.webchat__modal-dialog.view-code-dialog) { +:global(.webchat-fluent).theme :global(.webchat__modal-dialog).view-code-dialog { :global(.webchat__modal-dialog__close-button-layout) { display: flex; padding: var(--webchat-spacingVerticalS) var(--webchat-spacingHorizontalS); @@ -548,7 +548,7 @@ width: unset; } - :global(.code-block-copy-button) { + .code-block-copy-button { --webchat__code-block__copy-button--color: var(--webchat-colorNeutralForeground1); --webchat__code-block__copy-button--background: var(--webchat-colorNeutralBackground3); @@ -567,25 +567,25 @@ padding: var(--webchat-spacingVerticalS) var(--webchat-spacingHorizontalM); } - :global(.view-code-dialog__title) { + .view-code-dialog__title { font-size: var(--webchat-fontSizeBase300); font-weight: var(--webchat-fontWeightSemibold); line-height: var(--webchat-lineHeightBase300); } - :global(.view-code-dialog__body) { + .view-code-dialog__body { font-family: var(--webchat-fontFamilyMonospace); font-size: var(--webchat-fontSizeBase300); margin: 0 calc(var(--webchat-spacingHorizontalM) * -1); padding: 0 var(--webchat-spacingHorizontalM); } - :global(.code-block__body) { + .code-block__body { /* TODO: what is the right value? The value below matches "normal" for block display */ line-height: normal; } - :global(.view-code-dialog__footer) { + .view-code-dialog__footer { color: var(--webchat-colorNeutralForeground4); font-size: var(--webchat-fontSizeBase100); line-height: var(--webchat-lineHeightBase100); diff --git a/packages/fluent-theme/src/private/fluentCSSModuleOverrides.spec.ts b/packages/fluent-theme/src/private/fluentCSSModuleOverrides.spec.ts index 5814c7d9b9..0b0a27e8c4 100644 --- a/packages/fluent-theme/src/private/fluentCSSModuleOverrides.spec.ts +++ b/packages/fluent-theme/src/private/fluentCSSModuleOverrides.spec.ts @@ -6,7 +6,7 @@ import { readdirSync, readFileSync } from 'fs'; import { transform } from 'lightningcss'; import { join } from 'path'; -import { themeOverrides } from './fluentCSSModuleOverrides'; +import { partGroupingDecoratorOverrides, themeOverrides } from './fluentCSSModuleOverrides'; const PACKAGES_PATH = join(__dirname, '../../..'); @@ -52,6 +52,7 @@ function getGlobalClassNames(path: string): readonly string[] { const webChatLocalNames = new Set([...listCSSModules('component'), ...listCSSModules('bundle')].flatMap(getLocalNames)); const OVERRIDES_BY_FLUENT_CSS_MODULE: Readonly> = Object.freeze({ + 'fluent-theme/src/components/activity/PartGroupingDecorator.module.css': partGroupingDecoratorOverrides, 'fluent-theme/src/components/theme/Theme.module.css': themeOverrides }); diff --git a/packages/fluent-theme/src/private/fluentCSSModuleOverrides.ts b/packages/fluent-theme/src/private/fluentCSSModuleOverrides.ts index 822fe29fdc..87de9ede7d 100644 --- a/packages/fluent-theme/src/private/fluentCSSModuleOverrides.ts +++ b/packages/fluent-theme/src/private/fluentCSSModuleOverrides.ts @@ -1,10 +1,26 @@ // Local names of Web Chat CSS modules that each Fluent CSS module styles, kept sorted. // FluentThemeProvider passes Fluent's class names for them to Web Chat's useStyles() through , // so Fluent rules select them as local names (`.thumb-button`), not as readable class names (`:global(.thumb-button)`). -// fluentCSSModuleOverrides.spec.js checks these lists against the CSS. +// fluentCSSModuleOverrides.spec.ts checks these lists against the CSS. +// PartGroupingDecorator.module.css +const partGroupingDecoratorOverrides: readonly string[] = Object.freeze([ + 'code-block-content', + 'code-block-content__code-block', + 'code-block-content__header', + 'code-block-copy-button' +]); + +// Theme.module.css const themeOverrides: readonly string[] = Object.freeze([ - // Feedback + 'code-block', + 'code-block-content', + 'code-block-content__code-block', + 'code-block-copy-button', + 'code-block-copy-button__icon', + 'code-block__body', + 'code-block__theme--github-dark-default', + 'code-block__theme--github-light-default', 'feedback-form', 'feedback-form__cancel-button', 'feedback-form__form', @@ -15,7 +31,11 @@ const themeOverrides: readonly string[] = Object.freeze([ 'thumb-button', 'thumb-button--large', 'thumb-button__image', - 'thumb-button__input' + 'thumb-button__input', + 'view-code-dialog', + 'view-code-dialog__body', + 'view-code-dialog__footer', + 'view-code-dialog__title' ]); -export { themeOverrides }; +export { partGroupingDecoratorOverrides, themeOverrides }; diff --git a/packages/fluent-theme/src/private/fluentCSSModuleTheme.ts b/packages/fluent-theme/src/private/fluentCSSModuleTheme.ts index 2b64a19e83..daa88cd5a5 100644 --- a/packages/fluent-theme/src/private/fluentCSSModuleTheme.ts +++ b/packages/fluent-theme/src/private/fluentCSSModuleTheme.ts @@ -1,11 +1,15 @@ import { combineCSSModules } from 'botframework-webchat/internal.js'; +import partGroupingDecoratorStyles from '../components/activity/PartGroupingDecorator.module.css'; import themeStyles from '../components/theme/Theme.module.css'; -import { themeOverrides } from './fluentCSSModuleOverrides'; +import { partGroupingDecoratorOverrides, themeOverrides } from './fluentCSSModuleOverrides'; import pickCSSModule from './pickCSSModule'; // Fluent's class names for the Web Chat elements it styles, for in FluentThemeProvider. // Built once, so every useStyles() below the composer gets a stable theme. -const fluentCSSModuleTheme = combineCSSModules(pickCSSModule(themeStyles, themeOverrides)); +const fluentCSSModuleTheme = combineCSSModules( + pickCSSModule(partGroupingDecoratorStyles, partGroupingDecoratorOverrides), + pickCSSModule(themeStyles, themeOverrides) +); export default fluentCSSModuleTheme; From f6f25baa2d0840ccea6a8c6e1b338262d1792383 Mon Sep 17 00:00:00 2001 From: Eugene Olonov Date: Fri, 30 Oct 2026 09:48:37 -0700 Subject: [PATCH 03/24] Fluent: style the attachment bar and text area through the CSS module theme --- .../src/components/sendBox/SendBox.module.css | 22 +++++++++---------- .../src/components/theme/Theme.module.css | 6 ++--- .../private/fluentCSSModuleOverrides.spec.ts | 3 ++- .../src/private/fluentCSSModuleOverrides.ts | 21 +++++++++++++++++- .../src/private/fluentCSSModuleTheme.ts | 4 +++- 5 files changed, 38 insertions(+), 18 deletions(-) diff --git a/packages/fluent-theme/src/components/sendBox/SendBox.module.css b/packages/fluent-theme/src/components/sendBox/SendBox.module.css index 522205a042..c07dbc7b5b 100644 --- a/packages/fluent-theme/src/components/sendBox/SendBox.module.css +++ b/packages/fluent-theme/src/components/sendBox/SendBox.module.css @@ -77,31 +77,31 @@ } } -:global(.webchat-fluent) .sendbox__attachment-bar:global(.send-box-attachment-bar) { - :global(.send-box-attachment-bar__box) { +:global(.webchat-fluent) .sendbox__attachment-bar.send-box-attachment-bar { + .send-box-attachment-bar__box { gap: var(--webchat-spacingVerticalXS) var(--webchat-spacingHorizontalXS); } - &:global(.send-box-attachment-bar--as-list-item) { + &.send-box-attachment-bar--as-list-item { margin-inline-end: calc(-1 * var(--webchat-spacingHorizontalS)); } - &:global(.send-box-attachment-bar--as-list-item .send-box-attachment-bar__box:not(:empty)) { + &.send-box-attachment-bar--as-list-item .send-box-attachment-bar__box:not(:empty) { padding: var(--webchat-spacingVerticalXS) var(--webchat-spacingHorizontalXS); } - :global(.send-box-attachment-bar-item.send-box-attachment-bar-item--as-list-item) { + .send-box-attachment-bar-item.send-box-attachment-bar-item--as-list-item { box-shadow: var(--webchat-shadow2); border-radius: var(--webchat-borderRadiusMedium); padding: var(--webchat-spacingVerticalXXS) var(--webchat-spacingHorizontalXXS); } - :global(.send-box-attachment-bar-item.send-box-attachment-bar-item--as-thumbnail) { + .send-box-attachment-bar-item.send-box-attachment-bar-item--as-thumbnail { border: var(--webchat-strokeWidthThin) solid var(--webchat-colorNeutralStroke1Selected); border-radius: var(--webchat-borderRadiusXLarge); } - :global(.send-box-attachment-bar-item .send-box-attachment-bar-item__delete-button) { + .send-box-attachment-bar-item .send-box-attachment-bar-item__delete-button { background-color: var(--webchat-colorNeutralBackground1); border-radius: var(--webchat-borderRadiusMedium); color: var(--webchat-colorNeutralForeground1); @@ -128,14 +128,12 @@ } } - :global( - .send-box-attachment-bar-item.send-box-attachment-bar-item.send-box-attachment-bar-item.send-box-attachment-bar-item--as-list-item - .send-box-attachment-bar-item__preview - ) { + .send-box-attachment-bar-item.send-box-attachment-bar-item.send-box-attachment-bar-item.send-box-attachment-bar-item--as-list-item + .send-box-attachment-bar-item__preview { padding-inline: var(--webchat-spacingHorizontalS); } - :global(.send-box-attachment-bar-item-file-preview.send-box-attachment-bar-item-file-preview--as-list-item) { + .send-box-attachment-bar-item-file-preview.send-box-attachment-bar-item-file-preview--as-list-item { gap: var(--webchat-spacingVerticalS) var(--webchat-spacingHorizontalS); } } diff --git a/packages/fluent-theme/src/components/theme/Theme.module.css b/packages/fluent-theme/src/components/theme/Theme.module.css index 4d929967b3..88c3887971 100644 --- a/packages/fluent-theme/src/components/theme/Theme.module.css +++ b/packages/fluent-theme/src/components/theme/Theme.module.css @@ -235,8 +235,8 @@ /* Scrollbars */ :global(.webchat-fluent).theme .code-block-content .code-block-content__code-block, -:global(.webchat-fluent).theme :global(.send-box-attachment-bar.send-box-attachment-bar--as-list-item), -:global(.webchat-fluent).theme :global(.text-area.text-area--scroll), +:global(.webchat-fluent).theme .send-box-attachment-bar.send-box-attachment-bar--as-list-item, +:global(.webchat-fluent).theme .text-area.text-area--scroll, :global(.webchat-fluent).theme :global(.webchat__basic-transcript .webchat__basic-transcript__scrollable), :global(.webchat-fluent).theme :global(.render-markdown [data-math-type='block']), :global(.webchat-fluent).theme .view-code-dialog__body, @@ -268,7 +268,7 @@ } &:hover::-webkit-scrollbar-thumb, - &:has(> :global(.text-area-input:focus))::-webkit-scrollbar-thumb { + &:has(> .text-area-input:focus)::-webkit-scrollbar-thumb { background-clip: padding-box; background-color: var(--webchat-colorNeutralForeground5); border-radius: 10px; diff --git a/packages/fluent-theme/src/private/fluentCSSModuleOverrides.spec.ts b/packages/fluent-theme/src/private/fluentCSSModuleOverrides.spec.ts index 0b0a27e8c4..330b54e128 100644 --- a/packages/fluent-theme/src/private/fluentCSSModuleOverrides.spec.ts +++ b/packages/fluent-theme/src/private/fluentCSSModuleOverrides.spec.ts @@ -6,7 +6,7 @@ import { readdirSync, readFileSync } from 'fs'; import { transform } from 'lightningcss'; import { join } from 'path'; -import { partGroupingDecoratorOverrides, themeOverrides } from './fluentCSSModuleOverrides'; +import { partGroupingDecoratorOverrides, sendBoxOverrides, themeOverrides } from './fluentCSSModuleOverrides'; const PACKAGES_PATH = join(__dirname, '../../..'); @@ -53,6 +53,7 @@ const webChatLocalNames = new Set([...listCSSModules('component'), ...listCSSMod const OVERRIDES_BY_FLUENT_CSS_MODULE: Readonly> = Object.freeze({ 'fluent-theme/src/components/activity/PartGroupingDecorator.module.css': partGroupingDecoratorOverrides, + 'fluent-theme/src/components/sendBox/SendBox.module.css': sendBoxOverrides, 'fluent-theme/src/components/theme/Theme.module.css': themeOverrides }); diff --git a/packages/fluent-theme/src/private/fluentCSSModuleOverrides.ts b/packages/fluent-theme/src/private/fluentCSSModuleOverrides.ts index 87de9ede7d..6a060e216d 100644 --- a/packages/fluent-theme/src/private/fluentCSSModuleOverrides.ts +++ b/packages/fluent-theme/src/private/fluentCSSModuleOverrides.ts @@ -11,6 +11,20 @@ const partGroupingDecoratorOverrides: readonly string[] = Object.freeze([ 'code-block-copy-button' ]); +// SendBox.module.css +const sendBoxOverrides: readonly string[] = Object.freeze([ + 'send-box-attachment-bar', + 'send-box-attachment-bar--as-list-item', + 'send-box-attachment-bar-item', + 'send-box-attachment-bar-item--as-list-item', + 'send-box-attachment-bar-item--as-thumbnail', + 'send-box-attachment-bar-item-file-preview', + 'send-box-attachment-bar-item-file-preview--as-list-item', + 'send-box-attachment-bar-item__delete-button', + 'send-box-attachment-bar-item__preview', + 'send-box-attachment-bar__box' +]); + // Theme.module.css const themeOverrides: readonly string[] = Object.freeze([ 'code-block', @@ -28,6 +42,11 @@ const themeOverrides: readonly string[] = Object.freeze([ 'feedback-form__form-header', 'feedback-form__submit-button', 'feedback-form__text-box', + 'send-box-attachment-bar', + 'send-box-attachment-bar--as-list-item', + 'text-area', + 'text-area--scroll', + 'text-area-input', 'thumb-button', 'thumb-button--large', 'thumb-button__image', @@ -38,4 +57,4 @@ const themeOverrides: readonly string[] = Object.freeze([ 'view-code-dialog__title' ]); -export { partGroupingDecoratorOverrides, themeOverrides }; +export { partGroupingDecoratorOverrides, sendBoxOverrides, themeOverrides }; diff --git a/packages/fluent-theme/src/private/fluentCSSModuleTheme.ts b/packages/fluent-theme/src/private/fluentCSSModuleTheme.ts index daa88cd5a5..53e49e15c3 100644 --- a/packages/fluent-theme/src/private/fluentCSSModuleTheme.ts +++ b/packages/fluent-theme/src/private/fluentCSSModuleTheme.ts @@ -1,14 +1,16 @@ import { combineCSSModules } from 'botframework-webchat/internal.js'; import partGroupingDecoratorStyles from '../components/activity/PartGroupingDecorator.module.css'; +import sendBoxStyles from '../components/sendBox/SendBox.module.css'; import themeStyles from '../components/theme/Theme.module.css'; -import { partGroupingDecoratorOverrides, themeOverrides } from './fluentCSSModuleOverrides'; +import { partGroupingDecoratorOverrides, sendBoxOverrides, themeOverrides } from './fluentCSSModuleOverrides'; import pickCSSModule from './pickCSSModule'; // Fluent's class names for the Web Chat elements it styles, for in FluentThemeProvider. // Built once, so every useStyles() below the composer gets a stable theme. const fluentCSSModuleTheme = combineCSSModules( pickCSSModule(partGroupingDecoratorStyles, partGroupingDecoratorOverrides), + pickCSSModule(sendBoxStyles, sendBoxOverrides), pickCSSModule(themeStyles, themeOverrides) ); From b75cc17eac15a58bf05fac661ae1c8e3fee4aebb Mon Sep 17 00:00:00 2001 From: Eugene Olonov Date: Fri, 30 Oct 2026 09:48:57 -0700 Subject: [PATCH 04/24] Fluent: style the transcript focus indicators through the CSS module theme --- .../activity/PartGroupingDecorator.module.css | 4 ++-- .../src/components/theme/Theme.module.css | 21 +++++++++---------- .../src/private/fluentCSSModuleOverrides.ts | 8 ++++++- 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/packages/fluent-theme/src/components/activity/PartGroupingDecorator.module.css b/packages/fluent-theme/src/components/activity/PartGroupingDecorator.module.css index 8083bb68a6..6300b0dd81 100644 --- a/packages/fluent-theme/src/components/activity/PartGroupingDecorator.module.css +++ b/packages/fluent-theme/src/components/activity/PartGroupingDecorator.module.css @@ -586,7 +586,7 @@ &.variant-fluent { --webchat-part-grouping__bubble--box-shadow: var(--webchat-shadow4); - :global(.stacked-layout .transcript-focus-area__activity-list) { + :global(.stacked-layout) .transcript-focus-area__activity-list { --webchat--collapsible-grouping__list--gap: var(--webchat-spacingVerticalXS); padding: var(--webchat-spacingVerticalNone) var(--webchat-spacingHorizontalNone); gap: var(--webchat-spacingVerticalXS); @@ -967,7 +967,7 @@ } } - :global(.stacked-layout .transcript-focus-area__activity-list) { + :global(.stacked-layout) .transcript-focus-area__activity-list { --webchat--collapsible-grouping__list--gap: var(--webchat-spacingVerticalS); padding: var(--webchat-spacingVerticalM) var(--webchat-spacingHorizontalM); } diff --git a/packages/fluent-theme/src/components/theme/Theme.module.css b/packages/fluent-theme/src/components/theme/Theme.module.css index 88c3887971..c79ce70ef5 100644 --- a/packages/fluent-theme/src/components/theme/Theme.module.css +++ b/packages/fluent-theme/src/components/theme/Theme.module.css @@ -285,7 +285,7 @@ } } -:global(.webchat-fluent).theme.variant-copilot :global(.transcript-focus-area) { +:global(.webchat-fluent).theme.variant-copilot .transcript-focus-area { --webchat__transcript--spacing: var(--webchat-spacingVerticalM); } @@ -295,28 +295,27 @@ } /* Transcript focus indicator color */ -:global(.webchat-fluent).theme :global(.transcript-focus-area__transcript-indicator) { +:global(.webchat-fluent).theme .transcript-focus-area__transcript-indicator { --webchat__color--transcript-visual-keyboard-indicator: var(--webchat-colorStrokeFocus2); } /* Transcript Content indicator without focus-visible state */ -:global(.webchat-fluent).theme :global(.transcript-focus-area:not(:focus-visible) .transcript-focus-area__indicator) { +:global(.webchat-fluent).theme .transcript-focus-area:not(:focus-visible) .transcript-focus-area__indicator { /* Hide activity focus when no focus-visible state */ display: none; } /* Transcript Focus indicator when content focused */ -:global(.webchat-fluent).theme - :global(.transcript-focus-area:focus-visible:has(.transcript-focus-area__content--focused)) { - :global(.transcript-focus-area__transcript-indicator) { +:global(.webchat-fluent).theme .transcript-focus-area:focus-visible:has(.transcript-focus-area__content--focused) { + .transcript-focus-area__transcript-indicator { /* Hide transcript focus when an activity is focused */ display: none; } } /* Transcript focused content */ -:global(.webchat-fluent).theme :global(.transcript-focus-area:focus-visible .transcript-focus-area__content--focused) { - :global(.transcript-focus-area__indicator) { +:global(.webchat-fluent).theme .transcript-focus-area:focus-visible .transcript-focus-area__content--focused { + .transcript-focus-area__indicator { /* Hide transcript activity focus when the content is focused */ display: none; } @@ -340,8 +339,7 @@ } } - &:has(:global(.collapsible-grouping)) - > :global(.transcript-focus-area__content-overlay .transcript-focus-area__indicator) { + &:has(:global(.collapsible-grouping)) > .transcript-focus-area__content-overlay .transcript-focus-area__indicator { display: none; } @@ -384,7 +382,8 @@ /* Transcript focused content copilot variant */ :global(.webchat-fluent).theme.variant-copilot - :global(.transcript-focus-area:focus-visible .transcript-focus-area__content--focused) { + .transcript-focus-area:focus-visible + .transcript-focus-area__content--focused { :global(.collapsible-grouping)::after { inset: -4px; } diff --git a/packages/fluent-theme/src/private/fluentCSSModuleOverrides.ts b/packages/fluent-theme/src/private/fluentCSSModuleOverrides.ts index 6a060e216d..69dd556969 100644 --- a/packages/fluent-theme/src/private/fluentCSSModuleOverrides.ts +++ b/packages/fluent-theme/src/private/fluentCSSModuleOverrides.ts @@ -8,7 +8,8 @@ const partGroupingDecoratorOverrides: readonly string[] = Object.freeze([ 'code-block-content', 'code-block-content__code-block', 'code-block-content__header', - 'code-block-copy-button' + 'code-block-copy-button', + 'transcript-focus-area__activity-list' ]); // SendBox.module.css @@ -51,6 +52,11 @@ const themeOverrides: readonly string[] = Object.freeze([ 'thumb-button--large', 'thumb-button__image', 'thumb-button__input', + 'transcript-focus-area', + 'transcript-focus-area__content--focused', + 'transcript-focus-area__content-overlay', + 'transcript-focus-area__indicator', + 'transcript-focus-area__transcript-indicator', 'view-code-dialog', 'view-code-dialog__body', 'view-code-dialog__footer', From 392714bb06bddb045f820e9743005519b8465e34 Mon Sep 17 00:00:00 2001 From: Eugene Olonov Date: Fri, 30 Oct 2026 09:49:13 -0700 Subject: [PATCH 05/24] Fluent: style the stacked layout, bubbles and carousel through the CSS module theme --- .../activity/PartGroupingDecorator.module.css | 160 +++++++++--------- .../src/components/theme/Theme.module.css | 6 +- .../src/private/fluentCSSModuleOverrides.ts | 25 +++ 3 files changed, 109 insertions(+), 82 deletions(-) diff --git a/packages/fluent-theme/src/components/activity/PartGroupingDecorator.module.css b/packages/fluent-theme/src/components/activity/PartGroupingDecorator.module.css index 6300b0dd81..b67fbbd5fa 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 .bubble .text-content__generated-badge) { + .stacked-layout .bubble :global(.text-content__generated-badge) { align-items: center; align-self: flex-start; background-color: var(--webchat-colorNeutralBackground5); @@ -58,7 +58,7 @@ padding-inline: var(--webchat-spacingHorizontalXS); } - :global(.message-status-loader) { + .message-status-loader { --webchat__component-icon--mask: unset; --webchat__component-icon--image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22100%22%20height%3D%22100%22%20viewBox%3D%220%200%20100%20100%22%3E%3Cdefs%3E%3ClinearGradient%20id%3D%22a%22%20x1%3D%22-.5%22%20x2%3D%221.5%22%20y1%3D%22.5%22%20y2%3D%22.5%22%20gradientUnits%3D%22objectBoundingBox%22%3E%3Cstop%20offset%3D%220%22%20stop-color%3D%22%23d161aa%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%23e96d80%22%2F%3E%3C%2FlinearGradient%3E%3ClinearGradient%20id%3D%22b%22%20x1%3D%22-.5%22%20x2%3D%221.5%22%20y1%3D%22.5%22%20y2%3D%22.5%22%20gradientUnits%3D%22objectBoundingBox%22%3E%3Cstop%20offset%3D%220%22%20stop-color%3D%22%2342a3a6%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%2373af69%22%2F%3E%3C%2FlinearGradient%3E%3ClinearGradient%20id%3D%22c%22%20x1%3D%22-.5%22%20x2%3D%221.5%22%20y1%3D%22.5%22%20y2%3D%22.5%22%20gradientUnits%3D%22objectBoundingBox%22%3E%3Cstop%20offset%3D%220%22%20stop-color%3D%22%2365a5dd%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%238173ee%22%2F%3E%3C%2FlinearGradient%3E%3C%2Fdefs%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22url(%23a)%22%3E%3Canimate%20attributeName%3D%22r%22%20begin%3D%220s%22%20calcMode%3D%22spline%22%20dur%3D%223.6s%22%20keySplines%3D%220.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%22%20keyTimes%3D%220%3B0.1667%3B0.3333%3B0.5%3B0.6667%3B0.8333%3B1%22%20repeatCount%3D%22indefinite%22%20values%3D%2250%3B0%3B0%3B0%3B0%3B49%3B49%22%2F%3E%3Canimate%20attributeName%3D%22opacity%22%20begin%3D%220s%22%20calcMode%3D%22spline%22%20dur%3D%223.6s%22%20keySplines%3D%220.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%22%20keyTimes%3D%220%3B0.1667%3B0.3333%3B0.5%3B0.6667%3B0.6668%3B1%22%20repeatCount%3D%22indefinite%22%20values%3D%221%3B0%3B0%3B0%3B0%3B1%3B1%22%2F%3E%3Canimate%20attributeName%3D%22cx%22%20begin%3D%220s%22%20calcMode%3D%22spline%22%20dur%3D%223.6s%22%20keySplines%3D%220.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%22%20keyTimes%3D%220%3B0.1667%3B0.3333%3B0.5%3B0.6667%3B0.8333%3B1%22%20repeatCount%3D%22indefinite%22%20values%3D%2250%3B100%3B0%3B0%3B0%3B50%3B50%22%2F%3E%3C%2Fcircle%3E%3Ccircle%20cy%3D%2250%22%20r%3D%220%22%20fill%3D%22url(%23b)%22%20opacity%3D%220%22%3E%3Canimate%20attributeName%3D%22r%22%20begin%3D%220s%22%20calcMode%3D%22spline%22%20dur%3D%223.6s%22%20keySplines%3D%220.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%22%20keyTimes%3D%220%3B0.1667%3B0.3333%3B0.5%3B0.6667%3B0.8333%3B1%22%20repeatCount%3D%22indefinite%22%20values%3D%220%3B49%3B49%3B0%3B0%3B0%3B0%22%2F%3E%3Canimate%20attributeName%3D%22opacity%22%20begin%3D%220s%22%20calcMode%3D%22spline%22%20dur%3D%223.6s%22%20keySplines%3D%220.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%22%20keyTimes%3D%220%3B0.0001%3B0.3333%3B0.5%3B0.6667%3B0.8333%3B1%22%20repeatCount%3D%22indefinite%22%20values%3D%220%3B1%3B1%3B0%3B0%3B0%3B0%22%2F%3E%3Canimate%20attributeName%3D%22cx%22%20begin%3D%220s%22%20calcMode%3D%22spline%22%20dur%3D%223.6s%22%20keySplines%3D%220.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%22%20keyTimes%3D%220%3B0.1667%3B0.3333%3B0.5%3B0.6667%3B0.8333%3B1%22%20repeatCount%3D%22indefinite%22%20values%3D%220%3B50%3B50%3B100%3B0%3B0%3B0%22%2F%3E%3C%2Fcircle%3E%3Ccircle%20cy%3D%2250%22%20r%3D%220%22%20fill%3D%22url(%23c)%22%20opacity%3D%220%22%3E%3Canimate%20attributeName%3D%22r%22%20begin%3D%220s%22%20calcMode%3D%22spline%22%20dur%3D%223.6s%22%20keySplines%3D%220.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%22%20keyTimes%3D%220%3B0.1667%3B0.3333%3B0.5%3B0.6667%3B0.8333%3B1%22%20repeatCount%3D%22indefinite%22%20values%3D%220%3B0%3B0%3B49%3B49%3B0%3B0%22%2F%3E%3Canimate%20attributeName%3D%22opacity%22%20begin%3D%220s%22%20calcMode%3D%22spline%22%20dur%3D%223.6s%22%20keySplines%3D%220.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%22%20keyTimes%3D%220%3B0.1667%3B0.1668%3B0.5%3B0.6667%3B0.8333%3B1%22%20repeatCount%3D%22indefinite%22%20values%3D%220%3B0%3B0%3B1%3B1%3B0%3B0%22%2F%3E%3Canimate%20attributeName%3D%22cx%22%20begin%3D%220s%22%20calcMode%3D%22spline%22%20dur%3D%223.6s%22%20keySplines%3D%220.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%22%20keyTimes%3D%220%3B0.1667%3B0.3333%3B0.5%3B0.6667%3B0.8333%3B1%22%20repeatCount%3D%22indefinite%22%20values%3D%220%3B0%3B0%3B50%3B50%3B100%3B0%22%2F%3E%3C%2Fcircle%3E%3C%2Fsvg%3E'); @@ -70,7 +70,7 @@ } } - :global(.stacked-layout .bubble .webchat__fileContent__badge) { + .stacked-layout .bubble :global(.webchat__fileContent__badge) { cursor: default; font-size: var(--webchat-fontSizeBase300); line-height: var(--webchat-lineHeightBase300); @@ -86,7 +86,7 @@ } } - :global(.stacked-layout .bubble .webchat__fileContent__downloadIcon) { + .stacked-layout .bubble :global(.webchat__fileContent__downloadIcon) { color: var(--webchat-colorBrandForegroundLink); } /* #endregion */ @@ -318,7 +318,7 @@ /* #endregion */ /* #region Header & collapsible grouping */ - :global(.stacked-layout .collapsible-grouping__header) { + .stacked-layout :global(.collapsible-grouping__header) { justify-content: flex-start; min-width: 0; padding: var(--webchat-spacingVerticalNone) var(--webchat-spacingHorizontalNone); @@ -350,12 +350,12 @@ } } - :global(.stacked-layout .collapsible-grouping__content .part-grouping-activity__activities) { + .stacked-layout :global(.collapsible-grouping__content) :global(.part-grouping-activity__activities) { --webchat--collapsible-grouping__list--gap: var(--webchat-spacingVerticalS); padding: var(--webchat-spacingVerticalL) var(--webchat-spacingHorizontalL); } - :global(.stacked-layout .collapsible-grouping) { + .stacked-layout :global(.collapsible-grouping) { display: grid; &:global(.collapsible-grouping--open) { @@ -363,7 +363,7 @@ } } - :global(.collapsible-grouping__content .stacked-layout .stacked-layout__title), + :global(.collapsible-grouping__content) .stacked-layout .stacked-layout__title, :global(.collapsible-grouping__title) { color: var(--webchat-colorNeutralForeground2); font-size: var(--webchat-fontSizeBase300); @@ -376,7 +376,7 @@ border: none; } - :global(.stacked-layout .stacked-layout__message-status) { + .stacked-layout .stacked-layout__message-status { --webchat__component-icon--size: 14px; color: var(--webchat-colorNeutralForeground2); @@ -386,21 +386,21 @@ :global(.collapsible-grouping) { max-width: 100%; - :global(.stacked-layout__message-status) { + .stacked-layout__message-status { height: unset; width: unset; - :global(.component-icon) { + .component-icon { height: 19px; width: 19px; } } - :global(.stacked-layout .activity-loader) { + .stacked-layout :global(.activity-loader) { display: none; } - :global(.part-grouping-decorator .bubble .bubble__content) { + :global(.part-grouping-decorator) .bubble .bubble__content { box-sizing: content-box; gap: var(--webchat-spacingVerticalXS); } @@ -408,47 +408,47 @@ /* #endregion */ /* #region Stacked layout & message status */ - :global(.stacked-layout) { + .stacked-layout { display: flex; flex-flow: column nowrap; margin-inline: var(--webchat-spacingHorizontalMNudge); - :global(.stacked-layout) { + .stacked-layout { margin-inline: var(--webchat-spacingHorizontalNone); position: static; } - :global(.stacked-layout__main .stacked-layout__title) { + .stacked-layout__main .stacked-layout__title { color: var(--webchat-colorNeutralForeground4); font-size: var(--webchat-fontSizeBase400); line-height: var(--webchat-lineHeightBase400); margin: var(--webchat-part-grouping__bubble--space-block) var(--webchat-part-grouping__bubble--space-inline); } - &:global(.stacked-layout--no-message .stacked-layout__attachment-row:first-child) { + &.stacked-layout--no-message .stacked-layout__attachment-row:first-child { margin-block-start: 0; } } - :global(.stacked-layout .stacked-layout__main .stacked-layout__message-status) { + .stacked-layout .stacked-layout__main .stacked-layout__message-status { margin-block-start: var(--webchat-part-grouping__message-status--space-block); } - :global(.stacked-layout .stacked-layout__content:has(.bubble)) { + .stacked-layout .stacked-layout__content:has(.bubble) { max-width: 100%; overflow: visible; } - :global(.stacked-layout .stacked-layout__status) { + .stacked-layout .stacked-layout__status { font-size: var(--webchat__font-size--small); line-height: var(--webchat__line-height--small); } /* #endregion */ /* #region Carousel layout */ - :global(.carousel-layout) { - :global(.carousel-filmstrip__avatar-gutter), - :global(.carousel-filmstrip__alignment-pad) { + .carousel-layout { + .carousel-filmstrip__avatar-gutter, + .carousel-filmstrip__alignment-pad { margin: 0; width: var(--webchat-spacingHorizontalMNudge); } @@ -456,19 +456,19 @@ /* #endregion */ /* #region Bubble surface */ - :global(.stacked-layout .bubble) { + .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 .bubble):has(:global(.activity-loader)) :global(.bubble__content) { + .stacked-layout .bubble:has(:global(.activity-loader)) .bubble__content { background: unset; box-shadow: unset; border-color: transparent; } - :global(.stacked-layout .bubble .bubble__content) { + .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); @@ -483,12 +483,14 @@ /* Forced colors remove the bubble shadow and fill, so draw a border as the default theme does. */ @media (forced-colors: active) { &:not(.part-grouping-decorator--group) - :global(.stacked-layout .bubble:not(:has(.activity-loader)) .bubble__content) { + .stacked-layout + .bubble:not(:has(:global(.activity-loader))) + .bubble__content { border: var(--webchat-strokeWidthThin) solid CanvasText; } } - :global(.stacked-layout .bubble .text-content) { + .stacked-layout .bubble :global(.text-content) { font-size: var(--webchat__font-size--medium); line-height: var(--webchat__line-height--medium); min-height: auto; @@ -505,7 +507,7 @@ /* #endregion */ /* #region Code blocks */ - :global(.stacked-layout) :global(.bubble) .code-block-content { + .stacked-layout .bubble .code-block-content { border-radius: var(--webchat-borderRadiusXLarge); border: var(--webchat-strokeWidthThin) solid var(--webchat-colorNeutralStroke1); font-size: var(--webchat-fontSizeBase300); @@ -536,7 +538,7 @@ /* #endregion */ /* #region Collapsible content */ - :global(.stacked-layout .bubble .collapsible-content) { + .stacked-layout .bubble :global(.collapsible-content) { :global(.collapsible-content__summary) { &:focus-visible { border-radius: var(--webchat-borderRadiusSmall); @@ -548,11 +550,11 @@ :global(.collapsible-content__content) { margin-block: var(--webchat-part-grouping__collapsible-content--space-block-start-end); - :global(.stacked-layout__attachment-list) { + .stacked-layout__attachment-list { gap: var(--webchat-spacingVerticalS); } - :global(.stacked-layout__attachment-row) { + .stacked-layout__attachment-row { margin: 0; :global(.text-content) { @@ -575,8 +577,8 @@ --webchat-part-grouping__collapsible-content--space-block-start-end: var(--webchat-spacingVerticalS) var(--webchat-spacingVerticalNone); - :global(.stacked-layout .stacked-layout__main .stacked-layout__title), - :global(.stacked-layout .stacked-layout__main .collapsible-content__summary) { + .stacked-layout .stacked-layout__main .stacked-layout__title, + .stacked-layout .stacked-layout__main :global(.collapsible-content__summary) { margin: var(--webchat-spacingVerticalXS) var(--webchat-part-grouping__bubble--space-inline); } } @@ -586,13 +588,13 @@ &.variant-fluent { --webchat-part-grouping__bubble--box-shadow: var(--webchat-shadow4); - :global(.stacked-layout) .transcript-focus-area__activity-list { + .stacked-layout .transcript-focus-area__activity-list { --webchat--collapsible-grouping__list--gap: var(--webchat-spacingVerticalXS); padding: var(--webchat-spacingVerticalNone) var(--webchat-spacingHorizontalNone); gap: var(--webchat-spacingVerticalXS); } - :global(.stacked-layout .stacked-layout__attachment-row) { + .stacked-layout .stacked-layout__attachment-row { margin-block-start: var(--webchat-spacingVerticalMNudge); } @@ -609,12 +611,12 @@ background-color var(--webchat-durationNormal) var(--webchat-curveDecelerateMid), box-shadow var(--webchat-durationNormal) var(--webchat-curveDecelerateMid); - :global(.collapsible-grouping__header .stacked-layout__message-status) { + :global(.collapsible-grouping__header) .stacked-layout__message-status { margin-block-start: var(--webchat-spacingVerticalNone); margin-inline-start: var(--webchat-spacingHorizontalNone); } - :global(.stacked-layout .stacked-layout__message-status) { + .stacked-layout .stacked-layout__message-status { margin-inline-end: calc(var(--webchat-spacingHorizontalS) * -1); } } @@ -626,15 +628,15 @@ } } - :global(.bubble) { + .bubble { position: static; } - :global(.border-flair) { + .border-flair { border-radius: var(--webchat-part-grouping__bubble--border-radius); } - :global(.bubble .bubble__content) { + .bubble .bubble__content { margin-block: calc(var(--webchat-spacingVerticalS) * -1); padding-block: var(--webchat-spacingVerticalS); } @@ -665,23 +667,23 @@ margin-inline-end: var(--webchat-spacingHorizontalSNudge); - :global(.stacked-layout) { + .stacked-layout { margin: 0; position: static; } - :global(.carousel-layout) { - :global(.carousel-filmstrip__avatar-gutter), - :global(.carousel-filmstrip__alignment-pad) { + .carousel-layout { + .carousel-filmstrip__avatar-gutter, + .carousel-filmstrip__alignment-pad { width: 0; } } - :global(.bubble) { + .bubble { position: static; width: var(--webchat-part-grouping__bubble--max-width); - :global(.bubble__content .adaptive-card-renderer > .ac-adaptiveCard) { + .bubble__content :global(.adaptive-card-renderer) > :global(.ac-adaptiveCard) { font-family: var(--webchat-fontFamilyBase); /* Remove double padding for adaptive cards inside bubble */ padding: unset !important; @@ -747,26 +749,26 @@ } &:not(.part-grouping-decorator--group) { - :global(.bubble__content) { + .bubble__content { display: flex; flex-direction: column; gap: var(--webchat-spacingVerticalS); } } - :global(.bubble .collapsible-content .collapsible-content__content) { + .bubble :global(.collapsible-content) :global(.collapsible-content__content) { margin-block: var(--webchat-spacingVerticalNone); - :global(.stacked-layout__attachment-list) { + .stacked-layout__attachment-list { margin-block-start: var(--webchat-spacingVerticalS); } - :global(.stacked-layout__attachment) { + .stacked-layout__attachment { max-width: var(--webchat-part-grouping__bubble--max-width); } } - :global(.stacked-layout__status) { + .stacked-layout__status { display: none; } } @@ -794,75 +796,75 @@ } } - :global(.bubble .bubble__content) { + .bubble .bubble__content { margin-block: calc(var(--webchat-spacingVerticalS) * -1); padding-block: var(--webchat-spacingVerticalS); - :global(.stacked-layout__bubble .stacked-layout__title), - :global(.stacked-layout__bubble .collapsible-content__summary) { + .stacked-layout__bubble .stacked-layout__title, + .stacked-layout__bubble :global(.collapsible-content__summary) { color: var(--webchat-colorNeutralForeground2); margin-block: var(--webchat-spacingVerticalXXS); } - :global(.stacked-loyout__bubble .stacked-layout__message-status), - :global(.stacked-layout__bubble .stacked-layout__title), - :global(.stacked-layout__bubble .collapsible-content__summary), - :global(.stacked-layout__bubble .text-content) { + :global(.stacked-loyout__bubble) .stacked-layout__message-status, + .stacked-layout__bubble .stacked-layout__title, + .stacked-layout__bubble :global(.collapsible-content__summary), + .stacked-layout__bubble :global(.text-content) { font-size: var(--webchat-fontSizeBase200); line-height: var(--webchat-lineHeightBase200); } - :global(.stacked-layout__bubble .text-content) { + .stacked-layout__bubble :global(.text-content) { color: var(--webchat-colorNeutralForeground3); } } } - &.part-grouping-decorator--from-bot:not(.part-grouping-decorator--group) :global(.bubble__content) { + &.part-grouping-decorator--from-bot:not(.part-grouping-decorator--group) .bubble__content { box-sizing: content-box; padding-block: var(--webchat-spacingVerticalM); padding-inline: var(--webchat-spacingHorizontalL); } - &:not(.part-grouping-decorator--group) :global(.stacked-layout .bubble .bubble__content) { + &:not(.part-grouping-decorator--group) .stacked-layout .bubble .bubble__content { border-width: var(--webchat-strokeWidthThin); border-style: solid; } - &:not(.part-grouping-decorator--group) :global(.bubble:not(.bubble--from-user) .bubble__content) { + &:not(.part-grouping-decorator--group) .bubble:not(.bubble--from-user) .bubble__content { anchor-name: --webchat-flair; max-width: unset; } - :global(.activity-status) { + .activity-status { margin: 0 0 var(--webchat-spacingHorizontalXXS); } - :global(.stacked-layout__status) { + .stacked-layout__status { order: -1; } - &:has(:global(.bubble--from-user)) { - :global(.bubble) { + &:has(.bubble--from-user) { + .bubble { margin-block-end: var(--webchat-spacingVerticalM); } } - &:has(:global(.bubble.bubble--from-user)), + &:has(.bubble.bubble--from-user), &:has(:global(.pre-chat-message-activity)), &:has(:global(.liner-message-activity)) { padding-inline-start: var(--webchat-spacingHorizontalNone); } - :global(.bubble .text-content .text-content__generated-badge) { + .bubble :global(.text-content) :global(.text-content__generated-badge) { display: none; } - :global(.stacked-layout) { + .stacked-layout { margin-inline: var(--webchat-spacingHorizontalSNudge); } - :global(.stacked-layout:has(.collapsible-grouping)) { + .stacked-layout:has(:global(.collapsible-grouping)) { margin-inline: var(--webchat-spacingHorizontalNone); } @@ -927,33 +929,33 @@ :global(.collapsible-grouping__content:not(.collapsible-grouping__content--open) .collapsible-grouping__list) { padding: var(--webchat-spacingVerticalNone) var(--webchat-spacingHorizontalNone); - > *:not(:has(:global(.stacked-layout__message-status--incomplete))) { + > *:not(:has(.stacked-layout__message-status--incomplete)) { height: 0; margin: 0; visibility: hidden; overflow: clip; } - > *:has(:global(.stacked-layout__message-status--incomplete)) { + > *:has(.stacked-layout__message-status--incomplete) { /* We use animation so we can reset needed properties immediately upon switching classes */ animation: collapsible-grouping__fade-in var(--webchat__transition-duration) var(--webchat__transition-duration) var(--webchat__transition-easing) both; pointer-events: none; - :global(.stacked-layout__message-status--incomplete), + .stacked-layout__message-status--incomplete, :global(.collapsible-content__chevron) { visibility: hidden; } } - &:has(:global(.stacked-layout__message-status--incomplete)) { + &:has(.stacked-layout__message-status--incomplete) { padding: var(--webchat-spacingVerticalNone) var(--webchat-spacingHorizontalNone); transform: translate(-4px, -20px); margin-block-end: calc(-20px - var(--webchat--collapsible-grouping__list--gap)); } } - :global(.stacked-layout .stacked-layout__message-status) { + .stacked-layout .stacked-layout__message-status { --webchat__component-icon--size: 14px; color: var(--webchat-colorNeutralForeground2); @@ -961,18 +963,18 @@ margin-inline: var(--webchat-spacingHorizontalNone) var(--webchat-spacingHorizontalS); width: 20px; - :global(.component-icon) { + .component-icon { height: unset; width: unset; } } - :global(.stacked-layout) .transcript-focus-area__activity-list { + .stacked-layout .transcript-focus-area__activity-list { --webchat--collapsible-grouping__list--gap: var(--webchat-spacingVerticalS); padding: var(--webchat-spacingVerticalM) var(--webchat-spacingHorizontalM); } - :global(.border-flair) { + .border-flair { border-radius: var(--webchat-borderRadius2XLarge); inset: anchor(top) anchor(right) anchor(bottom) anchor(left); position-anchor: --webchat-flair; diff --git a/packages/fluent-theme/src/components/theme/Theme.module.css b/packages/fluent-theme/src/components/theme/Theme.module.css index c79ce70ef5..3f8a0c0292 100644 --- a/packages/fluent-theme/src/components/theme/Theme.module.css +++ b/packages/fluent-theme/src/components/theme/Theme.module.css @@ -321,12 +321,12 @@ } /* Transcript activity focused directly */ - > :global(.webchat__focus-trap > .webchat__basic-transcript__activity-body .bubble) { + > :global(.webchat__focus-trap) > :global(.webchat__basic-transcript__activity-body) .bubble { display: grid; grid-template-areas: 'focused-content'; position: static; - :global(.bubble__content) { + .bubble__content { grid-area: focused-content; } @@ -354,7 +354,7 @@ position: absolute; } - :global(.activity-decorator .bubble .bubble__nub-pad) { + :global(.activity-decorator) .bubble .bubble__nub-pad { display: none; } diff --git a/packages/fluent-theme/src/private/fluentCSSModuleOverrides.ts b/packages/fluent-theme/src/private/fluentCSSModuleOverrides.ts index 69dd556969..7c9563d816 100644 --- a/packages/fluent-theme/src/private/fluentCSSModuleOverrides.ts +++ b/packages/fluent-theme/src/private/fluentCSSModuleOverrides.ts @@ -5,10 +5,32 @@ // PartGroupingDecorator.module.css const partGroupingDecoratorOverrides: readonly string[] = Object.freeze([ + 'activity-status', + 'border-flair', + 'bubble', + 'bubble--from-user', + 'bubble__content', + 'carousel-filmstrip__alignment-pad', + 'carousel-filmstrip__avatar-gutter', + 'carousel-layout', 'code-block-content', 'code-block-content__code-block', 'code-block-content__header', 'code-block-copy-button', + 'component-icon', + 'message-status-loader', + 'stacked-layout', + 'stacked-layout--no-message', + 'stacked-layout__attachment', + 'stacked-layout__attachment-list', + 'stacked-layout__attachment-row', + 'stacked-layout__bubble', + 'stacked-layout__content', + 'stacked-layout__main', + 'stacked-layout__message-status', + 'stacked-layout__message-status--incomplete', + 'stacked-layout__status', + 'stacked-layout__title', 'transcript-focus-area__activity-list' ]); @@ -28,6 +50,9 @@ const sendBoxOverrides: readonly string[] = Object.freeze([ // Theme.module.css const themeOverrides: readonly string[] = Object.freeze([ + 'bubble', + 'bubble__content', + 'bubble__nub-pad', 'code-block', 'code-block-content', 'code-block-content__code-block', From 98b9a979b273e5da4a2b0d117232458600d6c465 Mon Sep 17 00:00:00 2001 From: Eugene Olonov Date: Fri, 30 Oct 2026 09:49:35 -0700 Subject: [PATCH 06/24] Fluent: style grouping, text, citations and Adaptive Cards through the CSS module theme No Fluent CSS module selects a Web Chat CSS module class through :global() anymore; fluentCSSModuleOverrides.spec.ts now checks this for every Fluent CSS module. --- .../activity/PartGroupingDecorator.module.css | 132 +++++++++--------- .../src/components/theme/Theme.module.css | 14 +- .../private/fluentCSSModuleOverrides.spec.ts | 14 +- .../src/private/fluentCSSModuleOverrides.ts | 38 +++++ 4 files changed, 124 insertions(+), 74 deletions(-) diff --git a/packages/fluent-theme/src/components/activity/PartGroupingDecorator.module.css b/packages/fluent-theme/src/components/activity/PartGroupingDecorator.module.css index b67fbbd5fa..4cbbb6c6ed 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 */ - .stacked-layout .bubble :global(.text-content__generated-badge) { + .stacked-layout .bubble .text-content__generated-badge { align-items: center; align-self: flex-start; background-color: var(--webchat-colorNeutralBackground5); @@ -92,7 +92,7 @@ /* #endregion */ /* #region Markdown & citations */ - :global(.render-markdown) a[href]:not(:global(.render-markdown__pure-identifier)) { + .render-markdown a[href]:not(:global(.render-markdown__pure-identifier)) { color: var(--webchat-colorBrandForegroundLink); text-decoration-color: transparent; @@ -125,7 +125,7 @@ } } - :global(.render-markdown :is(.render-markdown__citation, .render-markdown__pure-identifier)) { + .render-markdown :is(:global(.render-markdown__citation), :global(.render-markdown__pure-identifier)) { align-items: center; background-color: var(--webchat-colorNeutralBackground3); border-radius: var(--webchat-borderRadiusMedium); @@ -190,18 +190,18 @@ /* #endregion */ /* #region Citation summary */ - :global(.link-definitions__header) { + .link-definitions__header { border-radius: var(--webchat-borderRadiusMedium); - :global(.link-definitions__header-text) { + .link-definitions__header-text { color: var(--webchat-colorNeutralForeground3); } - :global(.link-definitions__message-sensitivity-label-text) { + .link-definitions__message-sensitivity-label-text { color: var(--webchat-colorNeutralForeground4); } - :global(.link-definitions__header-chevron) { + .link-definitions__header-chevron { color: var(--webchat-colorNeutralForeground3); font-size: var(--webchat__font-size--small); } @@ -212,23 +212,23 @@ } } - :global(.link-definitions) { + .link-definitions { --webchat__citation__external-link--mask: var(--webchat-externalLink-mask); --webchat__citation__link--max-width: var(--webchat-externalLink-maxWidth); - :global(.link-definitions__list) { + .link-definitions__list { color: var(--webchat__color--subtle); display: flex; flex-flow: row wrap; gap: var(--webchat-spacingHorizontalS); } - :global(.link-definitions__list-item) { + .link-definitions__list-item { border-radius: var(--webchat-borderRadiusMedium); max-width: var(--webchat__citation__link--max-width); } - :global(.link-definitions__list-item-box) { + .link-definitions__list-item-box { background-color: var(--webchat-colorNeutralBackground3); border-radius: var(--webchat-borderRadiusMedium); border: var(--webchat-strokeWidthThin) solid var(--webchat-colorNeutralStroke2); @@ -246,14 +246,14 @@ } } - :global(.link-definitions__list-item-body) { + .link-definitions__list-item-body { font-family: inherit; gap: var(--webchat-spacingHorizontalSNudge); min-width: 0; padding: 0; } - :global(.link-definitions__badge) { + .link-definitions__badge { align-self: center; background-color: transparent; border-radius: 0; @@ -269,7 +269,7 @@ text-align: center; } - :global(.link-definitions__badge)::after { + .link-definitions__badge::after { border-right: var(--webchat-strokeWidthThin) solid var(--webchat-colorNeutralStroke2); bottom: 0; content: ''; @@ -281,17 +281,17 @@ width: 0; } - :global(.link-definitions__list-item-main-text) { + .link-definitions__list-item-main-text { gap: var(--webchat-spacingHorizontalXS); } - :global(.link-definitions__list-item-text) { + .link-definitions__list-item-text { color: currentColor; text-decoration: none; text-overflow: ellipsis; } - :global(.link-definitions__list-item-main-text):has(:global(.link-definitions__open-in-new-window-icon))::before { + .link-definitions__list-item-main-text:has(.link-definitions__open-in-new-window-icon)::before { align-self: center; background: currentColor; color: currentColor; @@ -310,22 +310,22 @@ } /* It seems Copilot does not show per-item sensitivity label, i.e. "General", etc. */ - :global(.link-definitions__list-item-badge), - :global(.link-definitions__open-in-new-window-icon) { + .link-definitions__list-item-badge, + .link-definitions__open-in-new-window-icon { display: none; } } /* #endregion */ /* #region Header & collapsible grouping */ - .stacked-layout :global(.collapsible-grouping__header) { + .stacked-layout .collapsible-grouping__header { justify-content: flex-start; min-width: 0; padding: var(--webchat-spacingVerticalNone) var(--webchat-spacingHorizontalNone); place-self: start; position: relative; - :global(.activity-button) { + .activity-button { background: transparent; border: none; cursor: pointer; @@ -350,29 +350,29 @@ } } - .stacked-layout :global(.collapsible-grouping__content) :global(.part-grouping-activity__activities) { + .stacked-layout .collapsible-grouping__content .part-grouping-activity__activities { --webchat--collapsible-grouping__list--gap: var(--webchat-spacingVerticalS); padding: var(--webchat-spacingVerticalL) var(--webchat-spacingHorizontalL); } - .stacked-layout :global(.collapsible-grouping) { + .stacked-layout .collapsible-grouping { display: grid; - &:global(.collapsible-grouping--open) { + &.collapsible-grouping--open { gap: var(--webchat-spacingVerticalM); } } - :global(.collapsible-grouping__content) .stacked-layout .stacked-layout__title, - :global(.collapsible-grouping__title) { + .collapsible-grouping__content .stacked-layout .stacked-layout__title, + .collapsible-grouping__title { color: var(--webchat-colorNeutralForeground2); font-size: var(--webchat-fontSizeBase300); font-weight: var(--webchat-fontWeightSemibold); line-height: var(--webchat-lineHeightBase300); } - :global(.collapsible-grouping), - :global(.collapsible-grouping__header) { + .collapsible-grouping, + .collapsible-grouping__header { border: none; } @@ -383,7 +383,7 @@ font-size: var(--webchat-fontSizeBase400); } - :global(.collapsible-grouping) { + .collapsible-grouping { max-width: 100%; .stacked-layout__message-status { @@ -490,7 +490,7 @@ } } - .stacked-layout .bubble :global(.text-content) { + .stacked-layout .bubble .text-content { font-size: var(--webchat__font-size--medium); line-height: var(--webchat__line-height--medium); min-height: auto; @@ -500,7 +500,7 @@ padding-block-end: 0; } - + :global(.text-content) { + + .text-content { margin-top: calc(var(--webchat-part-grouping__bubble--space-block) * -1); } } @@ -538,8 +538,8 @@ /* #endregion */ /* #region Collapsible content */ - .stacked-layout .bubble :global(.collapsible-content) { - :global(.collapsible-content__summary) { + .stacked-layout .bubble .collapsible-content { + .collapsible-content__summary { &:focus-visible { border-radius: var(--webchat-borderRadiusSmall); outline-offset: 4px; @@ -547,7 +547,7 @@ } } - :global(.collapsible-content__content) { + .collapsible-content__content { margin-block: var(--webchat-part-grouping__collapsible-content--space-block-start-end); .stacked-layout__attachment-list { @@ -557,7 +557,7 @@ .stacked-layout__attachment-row { margin: 0; - :global(.text-content) { + .text-content { padding-block: 0; } } @@ -578,7 +578,7 @@ var(--webchat-spacingVerticalNone); .stacked-layout .stacked-layout__main .stacked-layout__title, - .stacked-layout .stacked-layout__main :global(.collapsible-content__summary) { + .stacked-layout .stacked-layout__main .collapsible-content__summary { margin: var(--webchat-spacingVerticalXS) var(--webchat-part-grouping__bubble--space-inline); } } @@ -601,7 +601,7 @@ &.part-grouping-decorator--group { --webchat-part-grouping__bubble--min-height: 20px; - :global(.collapsible-grouping) { + .collapsible-grouping { anchor-name: --webchat-flair; border-radius: var(--webchat-part-grouping__bubble--border-radius); max-width: min(var(--webchat-part-grouping__bubble--max-width), 100%); @@ -611,7 +611,7 @@ background-color var(--webchat-durationNormal) var(--webchat-curveDecelerateMid), box-shadow var(--webchat-durationNormal) var(--webchat-curveDecelerateMid); - :global(.collapsible-grouping__header) .stacked-layout__message-status { + .collapsible-grouping__header .stacked-layout__message-status { margin-block-start: var(--webchat-spacingVerticalNone); margin-inline-start: var(--webchat-spacingHorizontalNone); } @@ -621,8 +621,8 @@ } } - &:has(:global(.collapsible-grouping--open)) { - :global(.collapsible-grouping) { + &:has(.collapsible-grouping--open) { + .collapsible-grouping { background-color: var(--webchat-part-grouping__bubble--background-color); box-shadow: var(--webchat-part-grouping__bubble--box-shadow); } @@ -683,7 +683,7 @@ position: static; width: var(--webchat-part-grouping__bubble--max-width); - .bubble__content :global(.adaptive-card-renderer) > :global(.ac-adaptiveCard) { + .bubble__content .adaptive-card-renderer > :global(.ac-adaptiveCard) { font-family: var(--webchat-fontFamilyBase); /* Remove double padding for adaptive cards inside bubble */ padding: unset !important; @@ -713,7 +713,7 @@ } :global(.ac-pushButton:active), - :global(.ac-pushButton.adaptive-card-renderer__action--selected) { + :global(.ac-pushButton).adaptive-card-renderer__action--selected { background-color: var(--webchat-colorNeutralBackground1Pressed); border-color: var(--webchat-colorNeutralStroke1Pressed); color: var(--webchat-colorNeutralForeground1Pressed); @@ -740,7 +740,7 @@ } :global(.ac-pushButton.primary:active), - :global(.ac-pushButton.primary.adaptive-card-renderer__action--selected) { + :global(.ac-pushButton):global(.primary).adaptive-card-renderer__action--selected { background-color: var(--webchat-colorBrandBackgroundPressed); border-color: transparent; color: var(--webchat-colorNeutralForegroundOnBrand); @@ -756,7 +756,7 @@ } } - .bubble :global(.collapsible-content) :global(.collapsible-content__content) { + .bubble .collapsible-content .collapsible-content__content { margin-block: var(--webchat-spacingVerticalNone); .stacked-layout__attachment-list { @@ -778,7 +778,7 @@ --webchat-part-grouping__text-content--space-block-inline: var(--webchat-spacingVerticalNone) var(--webchat-spacingHorizontalNone); - :global(.collapsible-grouping__content) { + .collapsible-grouping__content { border: var(--webchat-strokeWidthThin) solid transparent; border-radius: var(--webchat-part-grouping__bubble--border-radius); position: relative; @@ -789,8 +789,8 @@ } } - &:has(:global(.collapsible-grouping--open)) { - :global(.collapsible-grouping__content) { + &:has(.collapsible-grouping--open) { + .collapsible-grouping__content { background-color: var(--webchat-part-grouping__bubble--background-color); border-color: var(--webchat-part-grouping__bubble--border-color); } @@ -801,20 +801,20 @@ padding-block: var(--webchat-spacingVerticalS); .stacked-layout__bubble .stacked-layout__title, - .stacked-layout__bubble :global(.collapsible-content__summary) { + .stacked-layout__bubble .collapsible-content__summary { color: var(--webchat-colorNeutralForeground2); margin-block: var(--webchat-spacingVerticalXXS); } :global(.stacked-loyout__bubble) .stacked-layout__message-status, .stacked-layout__bubble .stacked-layout__title, - .stacked-layout__bubble :global(.collapsible-content__summary), - .stacked-layout__bubble :global(.text-content) { + .stacked-layout__bubble .collapsible-content__summary, + .stacked-layout__bubble .text-content { font-size: var(--webchat-fontSizeBase200); line-height: var(--webchat-lineHeightBase200); } - .stacked-layout__bubble :global(.text-content) { + .stacked-layout__bubble .text-content { color: var(--webchat-colorNeutralForeground3); } } @@ -856,7 +856,7 @@ padding-inline-start: var(--webchat-spacingHorizontalNone); } - .bubble :global(.text-content) :global(.text-content__generated-badge) { + .bubble .text-content .text-content__generated-badge { display: none; } @@ -864,36 +864,36 @@ margin-inline: var(--webchat-spacingHorizontalSNudge); } - .stacked-layout:has(:global(.collapsible-grouping)) { + .stacked-layout:has(.collapsible-grouping) { margin-inline: var(--webchat-spacingHorizontalNone); } - :global(.collapsible-grouping) { + .collapsible-grouping { margin-inline: var(--webchat-spacingHorizontalXS); } - :global(.collapsible-grouping .collapsible-grouping__chevron), - :global(.collapsible-content .collapsible-content__chevron) { + .collapsible-grouping .collapsible-grouping__chevron, + .collapsible-content .collapsible-content__chevron { transform: rotate(270deg); } - :global(.collapsible-grouping--open .collapsible-grouping__chevron), - :global(.collapsible-content[open] .collapsible-content__chevron) { + .collapsible-grouping--open .collapsible-grouping__chevron, + .collapsible-content[open] .collapsible-content__chevron { transform: rotate(360deg); } - :global(.collapsible-grouping__header) { + .collapsible-grouping__header { margin-inline: calc(var(--webchat-spacingHorizontalXS) * -1); } - :global(.collapsible-grouping__title) { + .collapsible-grouping__title { color: var(--webchat-colorNeutralForeground2); font-size: var(--webchat-fontSizeBase200); font-weight: var(--webchat-fontWeightSemibold); line-height: var(--webchat-lineHeightBase200); } - :global(.collapsible-grouping__content) { + .collapsible-grouping__content { anchor-name: --webchat-flair; background: var(--webchat-colorNeutralBackground1); background-clip: padding-box; @@ -903,14 +903,14 @@ visibility: unset; } - :global(.collapsible-grouping__list) > * { + .collapsible-grouping__list > * { interpolate-size: allow-keywords; transition: margin calc(var(--webchat__transition-duration) / 2) var(--webchat__transition-easing), height calc(var(--webchat__transition-duration) / 2) var(--webchat__transition-easing); } - :global(.collapsible-grouping__content--open .collapsible-grouping__list) > * { + .collapsible-grouping__content--open .collapsible-grouping__list > * { /* We use animation so we can reset needed properties immediately upon switching classes */ animation: collapsible-grouping__fade-in var(--webchat__transition-duration) calc(var(--webchat__transition-duration) / 4) var(--webchat__transition-easing) both; @@ -920,13 +920,13 @@ height var(--webchat__transition-duration) var(--webchat__transition-easing); } - :global(.collapsible-grouping__content:not(.collapsible-grouping__content--open)) { + .collapsible-grouping__content:not(.collapsible-grouping__content--open) { /* We use animation so we can reset needed properties immediately upon switching classes */ animation: collapsible-grouping__fade-in var(--webchat__transition-duration) var(--webchat__transition-duration) var(--webchat__transition-easing) both; } - :global(.collapsible-grouping__content:not(.collapsible-grouping__content--open) .collapsible-grouping__list) { + .collapsible-grouping__content:not(.collapsible-grouping__content--open) .collapsible-grouping__list { padding: var(--webchat-spacingVerticalNone) var(--webchat-spacingHorizontalNone); > *:not(:has(.stacked-layout__message-status--incomplete)) { @@ -943,7 +943,7 @@ pointer-events: none; .stacked-layout__message-status--incomplete, - :global(.collapsible-content__chevron) { + .collapsible-content__chevron { visibility: hidden; } } diff --git a/packages/fluent-theme/src/components/theme/Theme.module.css b/packages/fluent-theme/src/components/theme/Theme.module.css index 3f8a0c0292..55950bd63b 100644 --- a/packages/fluent-theme/src/components/theme/Theme.module.css +++ b/packages/fluent-theme/src/components/theme/Theme.module.css @@ -238,7 +238,7 @@ :global(.webchat-fluent).theme .send-box-attachment-bar.send-box-attachment-bar--as-list-item, :global(.webchat-fluent).theme .text-area.text-area--scroll, :global(.webchat-fluent).theme :global(.webchat__basic-transcript .webchat__basic-transcript__scrollable), -:global(.webchat-fluent).theme :global(.render-markdown [data-math-type='block']), +:global(.webchat-fluent).theme .render-markdown [data-math-type='block'], :global(.webchat-fluent).theme .view-code-dialog__body, :global(.webchat-fluent).theme :global(.view-code-dialog__code-body) { /* Edge uses -webkit-scrollbar if scrollbar-* is not set */ @@ -339,11 +339,11 @@ } } - &:has(:global(.collapsible-grouping)) > .transcript-focus-area__content-overlay .transcript-focus-area__indicator { + &:has(.collapsible-grouping) > .transcript-focus-area__content-overlay .transcript-focus-area__indicator { display: none; } - :global(.collapsible-grouping)::after { + .collapsible-grouping::after { border-radius: var(--webchat-part-grouping__bubble--border-radius); border: none; content: ''; @@ -384,7 +384,7 @@ :global(.webchat-fluent).theme.variant-copilot .transcript-focus-area:focus-visible .transcript-focus-area__content--focused { - :global(.collapsible-grouping)::after { + .collapsible-grouping::after { inset: -4px; } } @@ -612,7 +612,7 @@ } /* Activity button */ -:global(.webchat-fluent).theme :global(.activity-button) { +:global(.webchat-fluent).theme .activity-button { background: var(--webchat-colorNeutralBackground1); border-radius: var(--webchat-borderRadiusMedium); border: var(--webchat-strokeWidthThin) solid var(--webchat-colorNeutralStroke1); @@ -644,7 +644,7 @@ color: var(--webchat-colorNeutralForegroundDisabled); } - :global(.activity-button__icon) { + .activity-button__icon { height: 20px; width: 20px; @@ -657,7 +657,7 @@ } /* Pause button over animated images: a band in the button background around the focus indicator keeps it visible over any image */ -:global(.webchat-fluent).theme :global(.activity-button.animated-image-content__button):focus-visible::after { +:global(.webchat-fluent).theme .activity-button.animated-image-content__button:focus-visible::after { outline: var(--webchat-strokeWidthThick) solid var(--webchat-colorNeutralBackground1); @media (forced-colors: active) { diff --git a/packages/fluent-theme/src/private/fluentCSSModuleOverrides.spec.ts b/packages/fluent-theme/src/private/fluentCSSModuleOverrides.spec.ts index 330b54e128..76db9bb31c 100644 --- a/packages/fluent-theme/src/private/fluentCSSModuleOverrides.spec.ts +++ b/packages/fluent-theme/src/private/fluentCSSModuleOverrides.spec.ts @@ -4,7 +4,7 @@ import * as JestGlobals from '@jest/globals'; import { scenario } from '@testduet/given-when-then'; import { readdirSync, readFileSync } from 'fs'; import { transform } from 'lightningcss'; -import { join } from 'path'; +import { join, relative } from 'path'; import { partGroupingDecoratorOverrides, sendBoxOverrides, themeOverrides } from './fluentCSSModuleOverrides'; @@ -93,6 +93,18 @@ scenario( .and('the lists should be sorted', (_, results) => results.forEach(({ localNames }) => expect(localNames).toEqual([...localNames].sort())) ); + + bdd + .given('every Fluent CSS module', () => listCSSModules('fluent-theme')) + .when('reading the class names it selects through :global()', paths => + paths.map(path => ({ + path: relative(PACKAGES_PATH, path), + webChatGlobalClassNames: getGlobalClassNames(path).filter(className => webChatLocalNames.has(className)) + })) + ) + .then('none should be a local name of a Web Chat CSS module', (_, results) => + expect(results.filter(({ webChatGlobalClassNames }) => webChatGlobalClassNames.length)).toEqual([]) + ); }, JestGlobals ); diff --git a/packages/fluent-theme/src/private/fluentCSSModuleOverrides.ts b/packages/fluent-theme/src/private/fluentCSSModuleOverrides.ts index 7c9563d816..f6cf308d8b 100644 --- a/packages/fluent-theme/src/private/fluentCSSModuleOverrides.ts +++ b/packages/fluent-theme/src/private/fluentCSSModuleOverrides.ts @@ -5,7 +5,10 @@ // PartGroupingDecorator.module.css const partGroupingDecoratorOverrides: readonly string[] = Object.freeze([ + 'activity-button', 'activity-status', + 'adaptive-card-renderer', + 'adaptive-card-renderer__action--selected', 'border-flair', 'bubble', 'bubble--from-user', @@ -17,8 +20,36 @@ const partGroupingDecoratorOverrides: readonly string[] = Object.freeze([ 'code-block-content__code-block', 'code-block-content__header', 'code-block-copy-button', + 'collapsible-content', + 'collapsible-content__chevron', + 'collapsible-content__content', + 'collapsible-content__summary', + 'collapsible-grouping', + 'collapsible-grouping--open', + 'collapsible-grouping__chevron', + 'collapsible-grouping__content', + 'collapsible-grouping__content--open', + 'collapsible-grouping__header', + 'collapsible-grouping__list', + 'collapsible-grouping__title', 'component-icon', + 'link-definitions', + 'link-definitions__badge', + 'link-definitions__header', + 'link-definitions__header-chevron', + 'link-definitions__header-text', + 'link-definitions__list', + 'link-definitions__list-item', + 'link-definitions__list-item-badge', + 'link-definitions__list-item-body', + 'link-definitions__list-item-box', + 'link-definitions__list-item-main-text', + 'link-definitions__list-item-text', + 'link-definitions__message-sensitivity-label-text', + 'link-definitions__open-in-new-window-icon', 'message-status-loader', + 'part-grouping-activity__activities', + 'render-markdown', 'stacked-layout', 'stacked-layout--no-message', 'stacked-layout__attachment', @@ -31,6 +62,8 @@ const partGroupingDecoratorOverrides: readonly string[] = Object.freeze([ 'stacked-layout__message-status--incomplete', 'stacked-layout__status', 'stacked-layout__title', + 'text-content', + 'text-content__generated-badge', 'transcript-focus-area__activity-list' ]); @@ -50,6 +83,9 @@ const sendBoxOverrides: readonly string[] = Object.freeze([ // Theme.module.css const themeOverrides: readonly string[] = Object.freeze([ + 'activity-button', + 'activity-button__icon', + 'animated-image-content__button', 'bubble', 'bubble__content', 'bubble__nub-pad', @@ -61,6 +97,7 @@ const themeOverrides: readonly string[] = Object.freeze([ 'code-block__body', 'code-block__theme--github-dark-default', 'code-block__theme--github-light-default', + 'collapsible-grouping', 'feedback-form', 'feedback-form__cancel-button', 'feedback-form__form', @@ -68,6 +105,7 @@ const themeOverrides: readonly string[] = Object.freeze([ 'feedback-form__form-header', 'feedback-form__submit-button', 'feedback-form__text-box', + 'render-markdown', 'send-box-attachment-bar', 'send-box-attachment-bar--as-list-item', 'text-area', From 36d197bb249c5a0d4e5a64bd8d242d42744e7c05 Mon Sep 17 00:00:00 2001 From: Eugene Olonov Date: Fri, 30 Oct 2026 09:59:47 -0700 Subject: [PATCH 07/24] Give the activity button text a CSS module class, so Fluent styles it through the CSS module theme --- .../src/Attachment/Text/private/ActivityButton.module.css | 6 ++++++ .../src/Attachment/Text/private/ActivityButton.tsx | 2 +- .../components/activity/PartGroupingDecorator.module.css | 2 +- .../fluent-theme/src/private/fluentCSSModuleOverrides.ts | 1 + 4 files changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/component/src/Attachment/Text/private/ActivityButton.module.css b/packages/component/src/Attachment/Text/private/ActivityButton.module.css index 03af8f460a..308877ba7b 100644 --- a/packages/component/src/Attachment/Text/private/ActivityButton.module.css +++ b/packages/component/src/Attachment/Text/private/ActivityButton.module.css @@ -46,4 +46,10 @@ --webchat__component-icon--color: buttonText; } } + + .activity-button__text:not(*) { + /* No styles of its own: the rule declares the local name, so useStyles() returns it and themes can style it. */ + /* Lightning CSS does not export local names of empty rules, and ":not(*)" never matches. */ + display: none; + } } diff --git a/packages/component/src/Attachment/Text/private/ActivityButton.tsx b/packages/component/src/Attachment/Text/private/ActivityButton.tsx index 1e1f270f8a..8d29e018e0 100644 --- a/packages/component/src/Attachment/Text/private/ActivityButton.tsx +++ b/packages/component/src/Attachment/Text/private/ActivityButton.tsx @@ -64,7 +64,7 @@ const ActivityButton = forwardRef((props type="button" > {icon && } - {text && {text}} + {text && {text}} {children} ); diff --git a/packages/fluent-theme/src/components/activity/PartGroupingDecorator.module.css b/packages/fluent-theme/src/components/activity/PartGroupingDecorator.module.css index 4cbbb6c6ed..19a33f8dc7 100644 --- a/packages/fluent-theme/src/components/activity/PartGroupingDecorator.module.css +++ b/packages/fluent-theme/src/components/activity/PartGroupingDecorator.module.css @@ -341,7 +341,7 @@ } } - :global(.activity-button__text) { + .activity-button__text { font-size: 0; height: 0; overflow: clip; diff --git a/packages/fluent-theme/src/private/fluentCSSModuleOverrides.ts b/packages/fluent-theme/src/private/fluentCSSModuleOverrides.ts index f6cf308d8b..2c9bda2feb 100644 --- a/packages/fluent-theme/src/private/fluentCSSModuleOverrides.ts +++ b/packages/fluent-theme/src/private/fluentCSSModuleOverrides.ts @@ -6,6 +6,7 @@ // PartGroupingDecorator.module.css const partGroupingDecoratorOverrides: readonly string[] = Object.freeze([ 'activity-button', + 'activity-button__text', 'activity-status', 'adaptive-card-renderer', 'adaptive-card-renderer__action--selected', From fb284c4f5446fac0dd5bc6bec89d1215e67a235f Mon Sep 17 00:00:00 2001 From: Eugene Olonov Date: Fri, 30 Oct 2026 15:10:40 -0700 Subject: [PATCH 08/24] Declare lightningcss, which fluentCSSModuleOverrides.spec.ts imports, as a root dev dependency Same version as the one unplugin-lightningcss already installs (1.32.0), so the lock file only gains the root entry. --- package-lock.json | 1 + package.json | 1 + 2 files changed, 2 insertions(+) diff --git a/package-lock.json b/package-lock.json index ce9c6e540e..aed27bac8a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -96,6 +96,7 @@ "jest": "^29.7.0", "jest-image-snapshot": "^6.5.2", "keep-a-changelog": "^3.0.4", + "lightningcss": "^1.32.0", "lint-staged": "^17.0.5", "lolex": "^6.0.0", "minimatch": "^10.2.5", diff --git a/package.json b/package.json index 5f222d4243..da11629e94 100644 --- a/package.json +++ b/package.json @@ -275,6 +275,7 @@ "jest": "^29.7.0", "jest-image-snapshot": "^6.5.2", "keep-a-changelog": "^3.0.4", + "lightningcss": "^1.32.0", "lint-staged": "^17.0.5", "lolex": "^6.0.0", "minimatch": "^10.2.5", From baed602d760230f42b23c5b4d77e4d8b1e8a0b18 Mon Sep 17 00:00:00 2001 From: Eugene Olonov Date: Fri, 30 Oct 2026 15:10:40 -0700 Subject: [PATCH 09/24] Check that Fluent lists every Web Chat local name its CSS modules use Read local names with the build's CSS modules options, so grid-area names are not counted. pickCSSModule() reads the CSS module through a Map instead of a computed property. --- .../private/fluentCSSModuleOverrides.spec.ts | 41 +++++++++++++++---- .../fluent-theme/src/private/pickCSSModule.ts | 4 +- 2 files changed, 35 insertions(+), 10 deletions(-) diff --git a/packages/fluent-theme/src/private/fluentCSSModuleOverrides.spec.ts b/packages/fluent-theme/src/private/fluentCSSModuleOverrides.spec.ts index 76db9bb31c..95ea242acf 100644 --- a/packages/fluent-theme/src/private/fluentCSSModuleOverrides.spec.ts +++ b/packages/fluent-theme/src/private/fluentCSSModuleOverrides.spec.ts @@ -17,11 +17,15 @@ const listCSSModules = (packageName: string): readonly string[] => .filter(path => path.endsWith('.module.css') && !path.includes('__tests__')) .map(path => join(PACKAGES_PATH, packageName, 'src', path)); -// Local names as Lightning CSS (the CSS module compiler of the build) exports them. +// Local names as the build exports them (Lightning CSS with the CSS modules options of esbuildPlugins.mjs). const getLocalNames = (path: string): readonly string[] => Object.keys( - // eslint-disable-next-line security/detect-non-literal-fs-filename - transform({ code: readFileSync(path), cssModules: true, filename: path }).exports ?? {} + transform({ + // eslint-disable-next-line security/detect-non-literal-fs-filename + code: readFileSync(path), + cssModules: { animation: false, customIdents: false, grid: false, pattern: 'w[hash]_[local]' }, + filename: path + }).exports ?? {} ); // Class names selected through `:global(…)`, which only match readable class names. @@ -57,6 +61,11 @@ const OVERRIDES_BY_FLUENT_CSS_MODULE: Readonly 'fluent-theme/src/components/theme/Theme.module.css': themeOverrides }); +// Fluent's own classes that happen to share a name with a Web Chat class, not overrides. +const FLUENT_OWN_LOCAL_NAMES: ReadonlySet = Object.freeze( + new Set(['appearance--text', 'icon--attachment', 'icon--microphone', 'icon--send']) +); + scenario( 'Fluent overrides of Web Chat CSS modules', bdd => { @@ -96,14 +105,28 @@ scenario( bdd .given('every Fluent CSS module', () => listCSSModules('fluent-theme')) - .when('reading the class names it selects through :global()', paths => - paths.map(path => ({ - path: relative(PACKAGES_PATH, path), - webChatGlobalClassNames: getGlobalClassNames(path).filter(className => webChatLocalNames.has(className)) - })) + .when('reading its local names and the class names it selects through :global()', paths => + paths.map(path => { + const listedLocalNames = OVERRIDES_BY_FLUENT_CSS_MODULE[relative(PACKAGES_PATH, path)] ?? []; + + return { + path: relative(PACKAGES_PATH, path), + // A Web Chat local name in a Fluent rule only matches when it is listed for that module. + unlistedWebChatLocalNames: getLocalNames(path).filter( + localName => + webChatLocalNames.has(localName) && + !listedLocalNames.includes(localName) && + !FLUENT_OWN_LOCAL_NAMES.has(localName) + ), + webChatGlobalClassNames: getGlobalClassNames(path).filter(className => webChatLocalNames.has(className)) + }; + }) ) - .then('none should be a local name of a Web Chat CSS module', (_, results) => + .then('none should select a local name of a Web Chat CSS module through :global()', (_, results) => expect(results.filter(({ webChatGlobalClassNames }) => webChatGlobalClassNames.length)).toEqual([]) + ) + .and('every local name of a Web Chat CSS module should be listed', (_, results) => + expect(results.filter(({ unlistedWebChatLocalNames }) => unlistedWebChatLocalNames.length)).toEqual([]) ); }, JestGlobals diff --git a/packages/fluent-theme/src/private/pickCSSModule.ts b/packages/fluent-theme/src/private/pickCSSModule.ts index 180406ce41..4895301337 100644 --- a/packages/fluent-theme/src/private/pickCSSModule.ts +++ b/packages/fluent-theme/src/private/pickCSSModule.ts @@ -9,11 +9,13 @@ export default function pickCSSModule( cssModule: CSSModuleClasses, localNames: readonly string[] ): Readonly> { + const classNamesMap = new Map(Object.entries(cssModule)); + return Object.freeze( Object.fromEntries( localNames.map(localName => [ localName, - parse(string(`The CSS module does not define the local name "${localName}".`), cssModule[`${localName}`]) + parse(string(`The CSS module does not define the local name "${localName}".`), classNamesMap.get(localName)) ]) ) ); From 6aeacbf3372d7aa38872964313f8211ef06fa5d1 Mon Sep 17 00:00:00 2001 From: Eugene Olonov Date: Fri, 30 Oct 2026 17:47:38 -0700 Subject: [PATCH 10/24] Keep class names of CSS module rules without styles through --webchat-preserve-reason Lightning CSS drops empty rules and their class names. A rule that only declares a class name for themes now says why with "--webchat-preserve-reason: target-class"; once the class map is built, the shared CSS plugin removes the declaration, and the rule if it becomes empty, from the CSS. --- esbuildPlugins.mjs | 29 +++++++++++++++++-- .../Text/private/ActivityButton.module.css | 6 ++-- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/esbuildPlugins.mjs b/esbuildPlugins.mjs index 5bd2aac4d1..2f735547ed 100644 --- a/esbuildPlugins.mjs +++ b/esbuildPlugins.mjs @@ -1,6 +1,17 @@ +import { Buffer } from 'buffer'; +import { transform } from 'lightningcss'; import { fileURLToPath } from 'url'; import lightningCssPlugin from 'unplugin-lightningcss/esbuild'; +// A CSS module rule without styles of its own keeps its class name (e.g. for themes) with this declaration: +// Lightning CSS drops empty rules, and their class names with them. +// Once the class map is built, the declaration, and the rule if it becomes empty, are removed from the CSS. +const PRESERVE_REASON_PROPERTY = '--webchat-preserve-reason'; + +const removePreserveReasonVisitor = Object.freeze({ + Declaration: Object.freeze({ custom: Object.freeze({ [PRESERVE_REASON_PROPERTY]: () => [] }) }) +}); + // Due to a bug in unplugin, we cannot use the Lightning CSS unplugin directly. // https://github.com/unjs/unplugin/issues/546 // To workaround the bug, we are converting it back to esbuild plugin so we can apply a custom `filter`. @@ -26,8 +37,22 @@ const cssPlugin = { } }).setup({ ...build, - // eslint-disable-next-line require-unicode-regexp - onLoad: (_filter, fn) => build.onLoad({ filter: /(\.module_built\.css|\?css_module)$/ }, fn) + onLoad: (_filter, fn) => + // eslint-disable-next-line require-unicode-regexp + build.onLoad({ filter: /(\.module_built\.css|\?css_module)$/ }, async args => { + const result = await fn(args); + + return typeof result?.contents === 'string' && result.contents.includes(PRESERVE_REASON_PROPERTY) + ? { + ...result, + contents: transform({ + code: Buffer.from(result.contents), + filename: args.path, + visitor: removePreserveReasonVisitor + }).code.toString() + } + : result; + }) }); } }; diff --git a/packages/component/src/Attachment/Text/private/ActivityButton.module.css b/packages/component/src/Attachment/Text/private/ActivityButton.module.css index 308877ba7b..c63f83705b 100644 --- a/packages/component/src/Attachment/Text/private/ActivityButton.module.css +++ b/packages/component/src/Attachment/Text/private/ActivityButton.module.css @@ -47,9 +47,7 @@ } } - .activity-button__text:not(*) { - /* No styles of its own: the rule declares the local name, so useStyles() returns it and themes can style it. */ - /* Lightning CSS does not export local names of empty rules, and ":not(*)" never matches. */ - display: none; + .activity-button__text { + --webchat-preserve-reason: target-class; } } From c310cf0a25e2593935de8a347c3b04b9a9fc4892 Mon Sep 17 00:00:00 2001 From: Eugene Olonov Date: Fri, 30 Oct 2026 17:49:17 -0700 Subject: [PATCH 11/24] Move Fluent's rules for the feedback form and thumb buttons to override modules Rules move verbatim from Theme.module.css to overrides/FeedbackForm.module.css and overrides/ThumbButton.module.css, one per Web Chat CSS module. FluentThemeProvider passes override modules whole to the CSS module theme, so their local names need no list; the spec checks they only use Web Chat local names and Fluent's own scoping names. --- .../src/components/theme/Theme.module.css | 114 ------------------ .../src/overrides/FeedbackForm.module.css | 97 +++++++++++++++ .../src/overrides/ThumbButton.module.css | 15 +++ .../private/fluentCSSModuleOverrides.spec.ts | 61 ++++++++-- .../src/private/fluentCSSModuleOverrides.ts | 11 -- .../src/private/fluentCSSModuleTheme.ts | 9 +- 6 files changed, 172 insertions(+), 135 deletions(-) create mode 100644 packages/fluent-theme/src/overrides/FeedbackForm.module.css create mode 100644 packages/fluent-theme/src/overrides/ThumbButton.module.css diff --git a/packages/fluent-theme/src/components/theme/Theme.module.css b/packages/fluent-theme/src/components/theme/Theme.module.css index 55950bd63b..94e8138f7e 100644 --- a/packages/fluent-theme/src/components/theme/Theme.module.css +++ b/packages/fluent-theme/src/components/theme/Theme.module.css @@ -665,22 +665,6 @@ } } -/* Feedback button */ -:global(.webchat-fluent).theme .thumb-button, -:global(.webchat-fluent).theme .thumb-button.thumb-button--large { - .thumb-button__image { - color: var(--webchat-colorNeutralForeground1); - } - - &:has(.thumb-button__input:focus-visible) { - outline: var(--webchat-strokeWidthThick) solid var(--webchat-colorStrokeFocus2); - } - - &:has(.thumb-button__input[aria-disabled='true']) .thumb-button__image { - color: var(--webchat-colorNeutralForegroundDisabled); - } -} - /* Tooltip */ :global(.webchat-fluent).theme :global(.webchat__tooltip) { --webchat__tooltip-background: var(--tooltip-background, var(--webchat-colorNeutralBackground1)); @@ -695,101 +679,3 @@ padding: var(--webchat-spacingVerticalSNudge) var(--webchat-spacingHorizontalM); transition: opacity var(--webchat-durationNormal) var(--webchat-curveDecelerateMid); } - -/* Feedback Form */ -:global(.webchat-fluent).theme .feedback-form { - .feedback-form__form-header { - color: var(--webchat-colorNeutralForeground1); - font-size: var(--webchat-fontSizeBase300); - font-weight: var(--webchat-fontWeightRegular); - line-height: var(--webchat-lineHeightBase300); - } - - .feedback-form__form { - gap: var(--webchat-spacingVerticalXS); - } - - .feedback-form__form-footer { - color: var(--webchat-colorNeutralForeground1); - font-size: var(--webchat-fontSizeBase100); - font-weight: var(--webchat-fontWeightRegular); - line-height: var(--webchat-lineHeightBase200); - } - - .feedback-form__submit-button { - background-color: var(--webchat-colorBrandBackground); - border: var(--webchat-strokeWidthThin) solid var(--webchat-colorBrandBackground); - border-radius: var(--webchat-borderRadiusMedium); - color: var(--webchat-colorNeutralForegroundOnBrand); - cursor: pointer; - font-size: var(--webchat-fontSizeBase300); - padding: var(--webchat-spacingVerticalNone) var(--webchat-spacingHorizontalS); - transition: background-color var(--webchat-durationNormal) var(--webchat-curveDecelerateMid); - - &:hover { - background-color: var(--webchat-colorBrandBackgroundHover); - border: var(--webchat-strokeWidthThin) solid var(--webchat-colorBrandBackgroundHover); - } - - &:focus-visible { - outline: var(--webchat-strokeWidthThick) solid var(--webchat-colorStrokeFocus2); - outline-offset: calc(var(--webchat-strokeWidthThin) * -1); - } - - &:active { - background-color: var(--webchat-colorBrandBackgroundPressed); - border: var(--webchat-strokeWidthThin) solid var(--webchat-colorBrandBackgroundPressed); - } - } - - .feedback-form__cancel-button { - background-color: var(--webchat-colorNeutralBackground1); - border: var(--webchat-strokeWidthThin) solid var(--webchat-colorNeutralStroke1); - border-radius: var(--webchat-borderRadiusMedium); - color: var(--webchat-colorNeutralForeground1); - cursor: pointer; - font-size: var(--webchat-fontSizeBase300); - padding: var(--webchat-spacingVerticalNone) var(--webchat-spacingHorizontalS); - transition: background-color var(--webchat-durationNormal) var(--webchat-curveDecelerateMid); - - &:hover { - background-color: var(--webchat-colorNeutralBackground1Hover); - border: var(--webchat-strokeWidthThin) solid var(--webchat-colorNeutralStroke1Hover); - color: currentColor; - } - - &:focus-visible { - outline: var(--webchat-strokeWidthThick) solid var(--webchat-colorStrokeFocus2); - outline-offset: calc(var(--webchat-strokeWidthThin) * -1); - } - - &:active { - background-color: var(--webchat-colorNeutralBackground1Pressed); - border: var(--webchat-strokeWidthThin) solid var(--webchat-colorNeutralStroke1Pressed); - } - } - - .feedback-form__text-box { - background-color: var(--webchat-colorNeutralBackground1); - border-radius: var(--webchat-borderRadiusMedium); - border: var(--webchat-strokeWidthThin) solid var(--webchat-colorNeutralStroke1); - font-size: var(--webchat-fontSizeBase300); - line-height: var(--webchat-lineHeightBase300); - gap: var(--webchat-spacingVerticalSNudge); - padding: var(--webchat-spacingVerticalS) var(--webchat-spacingHorizontalM); - - &::after { - border-bottom-left-radius: var(--webchat-borderRadiusMedium); - border-bottom-right-radius: var(--webchat-borderRadiusMedium); - border-bottom: var(--webchat-strokeWidthThicker) solid var(--webchat-colorBrandForeground1); - clip-path: inset(calc(100% - var(--webchat-strokeWidthThicker)) 50% 0 50%); - } - &:focus-within { - border: var(--webchat-strokeWidthThin) solid var(--webchat-colorNeutralStroke1); - } - &:focus-within::after { - clip-path: inset(calc(100% - var(--webchat-strokeWidthThicker)) 0 0 0); - transition: clip-path var(--webchat-durationNormal) var(--webchat-curveDecelerateMid); - } - } -} diff --git a/packages/fluent-theme/src/overrides/FeedbackForm.module.css b/packages/fluent-theme/src/overrides/FeedbackForm.module.css new file mode 100644 index 0000000000..2523b8fa63 --- /dev/null +++ b/packages/fluent-theme/src/overrides/FeedbackForm.module.css @@ -0,0 +1,97 @@ +/* Feedback Form */ +:global(.webchat-fluent).theme .feedback-form { + .feedback-form__form-header { + color: var(--webchat-colorNeutralForeground1); + font-size: var(--webchat-fontSizeBase300); + font-weight: var(--webchat-fontWeightRegular); + line-height: var(--webchat-lineHeightBase300); + } + + .feedback-form__form { + gap: var(--webchat-spacingVerticalXS); + } + + .feedback-form__form-footer { + color: var(--webchat-colorNeutralForeground1); + font-size: var(--webchat-fontSizeBase100); + font-weight: var(--webchat-fontWeightRegular); + line-height: var(--webchat-lineHeightBase200); + } + + .feedback-form__submit-button { + background-color: var(--webchat-colorBrandBackground); + border: var(--webchat-strokeWidthThin) solid var(--webchat-colorBrandBackground); + border-radius: var(--webchat-borderRadiusMedium); + color: var(--webchat-colorNeutralForegroundOnBrand); + cursor: pointer; + font-size: var(--webchat-fontSizeBase300); + padding: var(--webchat-spacingVerticalNone) var(--webchat-spacingHorizontalS); + transition: background-color var(--webchat-durationNormal) var(--webchat-curveDecelerateMid); + + &:hover { + background-color: var(--webchat-colorBrandBackgroundHover); + border: var(--webchat-strokeWidthThin) solid var(--webchat-colorBrandBackgroundHover); + } + + &:focus-visible { + outline: var(--webchat-strokeWidthThick) solid var(--webchat-colorStrokeFocus2); + outline-offset: calc(var(--webchat-strokeWidthThin) * -1); + } + + &:active { + background-color: var(--webchat-colorBrandBackgroundPressed); + border: var(--webchat-strokeWidthThin) solid var(--webchat-colorBrandBackgroundPressed); + } + } + + .feedback-form__cancel-button { + background-color: var(--webchat-colorNeutralBackground1); + border: var(--webchat-strokeWidthThin) solid var(--webchat-colorNeutralStroke1); + border-radius: var(--webchat-borderRadiusMedium); + color: var(--webchat-colorNeutralForeground1); + cursor: pointer; + font-size: var(--webchat-fontSizeBase300); + padding: var(--webchat-spacingVerticalNone) var(--webchat-spacingHorizontalS); + transition: background-color var(--webchat-durationNormal) var(--webchat-curveDecelerateMid); + + &:hover { + background-color: var(--webchat-colorNeutralBackground1Hover); + border: var(--webchat-strokeWidthThin) solid var(--webchat-colorNeutralStroke1Hover); + color: currentColor; + } + + &:focus-visible { + outline: var(--webchat-strokeWidthThick) solid var(--webchat-colorStrokeFocus2); + outline-offset: calc(var(--webchat-strokeWidthThin) * -1); + } + + &:active { + background-color: var(--webchat-colorNeutralBackground1Pressed); + border: var(--webchat-strokeWidthThin) solid var(--webchat-colorNeutralStroke1Pressed); + } + } + + .feedback-form__text-box { + background-color: var(--webchat-colorNeutralBackground1); + border-radius: var(--webchat-borderRadiusMedium); + border: var(--webchat-strokeWidthThin) solid var(--webchat-colorNeutralStroke1); + font-size: var(--webchat-fontSizeBase300); + line-height: var(--webchat-lineHeightBase300); + gap: var(--webchat-spacingVerticalSNudge); + padding: var(--webchat-spacingVerticalS) var(--webchat-spacingHorizontalM); + + &::after { + border-bottom-left-radius: var(--webchat-borderRadiusMedium); + border-bottom-right-radius: var(--webchat-borderRadiusMedium); + border-bottom: var(--webchat-strokeWidthThicker) solid var(--webchat-colorBrandForeground1); + clip-path: inset(calc(100% - var(--webchat-strokeWidthThicker)) 50% 0 50%); + } + &:focus-within { + border: var(--webchat-strokeWidthThin) solid var(--webchat-colorNeutralStroke1); + } + &:focus-within::after { + clip-path: inset(calc(100% - var(--webchat-strokeWidthThicker)) 0 0 0); + transition: clip-path var(--webchat-durationNormal) var(--webchat-curveDecelerateMid); + } + } +} diff --git a/packages/fluent-theme/src/overrides/ThumbButton.module.css b/packages/fluent-theme/src/overrides/ThumbButton.module.css new file mode 100644 index 0000000000..213f3803de --- /dev/null +++ b/packages/fluent-theme/src/overrides/ThumbButton.module.css @@ -0,0 +1,15 @@ +/* Feedback button */ +:global(.webchat-fluent).theme .thumb-button, +:global(.webchat-fluent).theme .thumb-button.thumb-button--large { + .thumb-button__image { + color: var(--webchat-colorNeutralForeground1); + } + + &:has(.thumb-button__input:focus-visible) { + outline: var(--webchat-strokeWidthThick) solid var(--webchat-colorStrokeFocus2); + } + + &:has(.thumb-button__input[aria-disabled='true']) .thumb-button__image { + color: var(--webchat-colorNeutralForegroundDisabled); + } +} diff --git a/packages/fluent-theme/src/private/fluentCSSModuleOverrides.spec.ts b/packages/fluent-theme/src/private/fluentCSSModuleOverrides.spec.ts index 95ea242acf..943b5aaa0f 100644 --- a/packages/fluent-theme/src/private/fluentCSSModuleOverrides.spec.ts +++ b/packages/fluent-theme/src/private/fluentCSSModuleOverrides.spec.ts @@ -4,7 +4,7 @@ import * as JestGlobals from '@jest/globals'; import { scenario } from '@testduet/given-when-then'; import { readdirSync, readFileSync } from 'fs'; import { transform } from 'lightningcss'; -import { join, relative } from 'path'; +import { basename, join, relative, sep } from 'path'; import { partGroupingDecoratorOverrides, sendBoxOverrides, themeOverrides } from './fluentCSSModuleOverrides'; @@ -55,6 +55,21 @@ function getGlobalClassNames(path: string): readonly string[] { const webChatLocalNames = new Set([...listCSSModules('component'), ...listCSSModules('bundle')].flatMap(getLocalNames)); +const webChatCSSModuleNames = new Set( + [...listCSSModules('component'), ...listCSSModules('bundle')].map(path => basename(path)) +); + +const isOverrideModule = (path: string): boolean => path.includes(`${sep}overrides${sep}`); + +const fluentLocalNames = new Set( + listCSSModules('fluent-theme') + .filter(path => !isOverrideModule(path)) + .flatMap(getLocalNames) +); + +// eslint-disable-next-line security/detect-non-literal-fs-filename +const FLUENT_CSS_MODULE_THEME_SOURCE = readFileSync(join(__dirname, 'fluentCSSModuleTheme.ts'), 'utf8'); + const OVERRIDES_BY_FLUENT_CSS_MODULE: Readonly> = Object.freeze({ 'fluent-theme/src/components/activity/PartGroupingDecorator.module.css': partGroupingDecoratorOverrides, 'fluent-theme/src/components/sendBox/SendBox.module.css': sendBoxOverrides, @@ -111,13 +126,16 @@ scenario( return { path: relative(PACKAGES_PATH, path), - // A Web Chat local name in a Fluent rule only matches when it is listed for that module. - unlistedWebChatLocalNames: getLocalNames(path).filter( - localName => - webChatLocalNames.has(localName) && - !listedLocalNames.includes(localName) && - !FLUENT_OWN_LOCAL_NAMES.has(localName) - ), + // A Web Chat local name in a Fluent rule only matches when it is listed for that module, + // or when the rule is in an override module, which is passed whole. + unlistedWebChatLocalNames: isOverrideModule(path) + ? [] + : getLocalNames(path).filter( + localName => + webChatLocalNames.has(localName) && + !listedLocalNames.includes(localName) && + !FLUENT_OWN_LOCAL_NAMES.has(localName) + ), webChatGlobalClassNames: getGlobalClassNames(path).filter(className => webChatLocalNames.has(className)) }; }) @@ -128,6 +146,33 @@ scenario( .and('every local name of a Web Chat CSS module should be listed', (_, results) => expect(results.filter(({ unlistedWebChatLocalNames }) => unlistedWebChatLocalNames.length)).toEqual([]) ); + + bdd + .given('every Fluent override module', () => listCSSModules('fluent-theme').filter(isOverrideModule)) + .when('reading its local names', paths => + paths.map(path => ({ + // Besides Web Chat local names, only Fluent's own local names that scope the rules, e.g. ".theme". + // The composer adds their class names to Fluent's elements as well. + localNamesNotFromWebChatNorFluent: getLocalNames(path).filter( + localName => !webChatLocalNames.has(localName) && !fluentLocalNames.has(localName) + ), + name: basename(path), + path: relative(PACKAGES_PATH, path) + })) + ) + .then('should only use local names of Web Chat and of Fluent CSS modules', (_, results) => + expect( + results.filter(({ localNamesNotFromWebChatNorFluent }) => localNamesNotFromWebChatNorFluent.length) + ).toEqual([]) + ) + .and('should mirror a Web Chat CSS module', (_, results) => + expect(results.filter(({ name }) => !webChatCSSModuleNames.has(name))).toEqual([]) + ) + .and('should be passed to the CSS module theme', (_, results) => + expect( + results.filter(({ name }) => !FLUENT_CSS_MODULE_THEME_SOURCE.includes(`'../overrides/${name}'`)) + ).toEqual([]) + ); }, JestGlobals ); diff --git a/packages/fluent-theme/src/private/fluentCSSModuleOverrides.ts b/packages/fluent-theme/src/private/fluentCSSModuleOverrides.ts index 2c9bda2feb..2d39064890 100644 --- a/packages/fluent-theme/src/private/fluentCSSModuleOverrides.ts +++ b/packages/fluent-theme/src/private/fluentCSSModuleOverrides.ts @@ -99,23 +99,12 @@ const themeOverrides: readonly string[] = Object.freeze([ 'code-block__theme--github-dark-default', 'code-block__theme--github-light-default', 'collapsible-grouping', - 'feedback-form', - 'feedback-form__cancel-button', - 'feedback-form__form', - 'feedback-form__form-footer', - 'feedback-form__form-header', - 'feedback-form__submit-button', - 'feedback-form__text-box', 'render-markdown', 'send-box-attachment-bar', 'send-box-attachment-bar--as-list-item', 'text-area', 'text-area--scroll', 'text-area-input', - 'thumb-button', - 'thumb-button--large', - 'thumb-button__image', - 'thumb-button__input', 'transcript-focus-area', 'transcript-focus-area__content--focused', 'transcript-focus-area__content-overlay', diff --git a/packages/fluent-theme/src/private/fluentCSSModuleTheme.ts b/packages/fluent-theme/src/private/fluentCSSModuleTheme.ts index 53e49e15c3..763b02e62c 100644 --- a/packages/fluent-theme/src/private/fluentCSSModuleTheme.ts +++ b/packages/fluent-theme/src/private/fluentCSSModuleTheme.ts @@ -3,15 +3,20 @@ import { combineCSSModules } from 'botframework-webchat/internal.js'; import partGroupingDecoratorStyles from '../components/activity/PartGroupingDecorator.module.css'; import sendBoxStyles from '../components/sendBox/SendBox.module.css'; import themeStyles from '../components/theme/Theme.module.css'; +import thumbButtonOverrideStyles from '../overrides/ThumbButton.module.css'; +import feedbackFormOverrideStyles from '../overrides/FeedbackForm.module.css'; import { partGroupingDecoratorOverrides, sendBoxOverrides, themeOverrides } from './fluentCSSModuleOverrides'; import pickCSSModule from './pickCSSModule'; // Fluent's class names for the Web Chat elements it styles, for in FluentThemeProvider. -// Built once, so every useStyles() below the composer gets a stable theme. +// Each override module mirrors one Web Chat CSS module and is passed whole, in the order its rules had in +// Fluent's CSS, to keep the cascade. Built once, so every useStyles() below the composer gets a stable theme. const fluentCSSModuleTheme = combineCSSModules( pickCSSModule(partGroupingDecoratorStyles, partGroupingDecoratorOverrides), pickCSSModule(sendBoxStyles, sendBoxOverrides), - pickCSSModule(themeStyles, themeOverrides) + pickCSSModule(themeStyles, themeOverrides), + thumbButtonOverrideStyles, + feedbackFormOverrideStyles ); export default fluentCSSModuleTheme; From fb0812fb8e0c1b3089f0e901f03604bfffc0f274 Mon Sep 17 00:00:00 2001 From: Eugene Olonov Date: Fri, 30 Oct 2026 17:49:32 -0700 Subject: [PATCH 12/24] Move Fluent's rules for code blocks to override modules Rules move verbatim from PartGroupingDecorator.module.css and Theme.module.css to overrides/CodeBlock.module.css, CodeBlockContent.module.css, CodeBlockCopyButton.module.css and ViewCodeDialog.module.css. --- .../activity/PartGroupingDecorator.module.css | 28 --- .../src/components/theme/Theme.module.css | 191 ------------------ .../src/overrides/CodeBlock.module.css | 45 +++++ .../src/overrides/CodeBlockContent.module.css | 60 ++++++ .../overrides/CodeBlockCopyButton.module.css | 65 ++++++ .../src/overrides/ViewCodeDialog.module.css | 64 ++++++ .../src/private/fluentCSSModuleOverrides.ts | 15 +- .../src/private/fluentCSSModuleTheme.ts | 8 + 8 files changed, 243 insertions(+), 233 deletions(-) create mode 100644 packages/fluent-theme/src/overrides/CodeBlock.module.css create mode 100644 packages/fluent-theme/src/overrides/CodeBlockContent.module.css create mode 100644 packages/fluent-theme/src/overrides/CodeBlockCopyButton.module.css create mode 100644 packages/fluent-theme/src/overrides/ViewCodeDialog.module.css diff --git a/packages/fluent-theme/src/components/activity/PartGroupingDecorator.module.css b/packages/fluent-theme/src/components/activity/PartGroupingDecorator.module.css index 19a33f8dc7..8179d4d422 100644 --- a/packages/fluent-theme/src/components/activity/PartGroupingDecorator.module.css +++ b/packages/fluent-theme/src/components/activity/PartGroupingDecorator.module.css @@ -507,34 +507,6 @@ /* #endregion */ /* #region Code blocks */ - .stacked-layout .bubble .code-block-content { - border-radius: var(--webchat-borderRadiusXLarge); - border: var(--webchat-strokeWidthThin) solid var(--webchat-colorNeutralStroke1); - font-size: var(--webchat-fontSizeBase300); - font-weight: var(--webchat-fontWeightRegular); - margin-block: var(--webchat-spacingVerticalNone); - margin-inline: var(--webchat-part-grouping__code-block--space-inline); - - .code-block-content__header { - padding: var(--webchat-spacingVerticalM) var(--webchat-spacingHorizontalL); - } - - .code-block-content__code-block { - padding-block: var(--webchat-spacingVerticalM); - padding-inline: var(--webchat-spacingHorizontalL) var(--webchat-spacingHorizontalS); - } - - .code-block-copy-button { - --webchat__code-block__copy-button--color: var(--webchat-colorNeutralForeground1); - --webchat__code-block__copy-button--background: var(--webchat-colorNeutralBackground3); - - margin-block-start: var(--webchat-spacingVerticalM); - margin-inline-end: var(--webchat-spacingHorizontalL); - position: absolute; - right: 0; - top: 0; - } - } /* #endregion */ /* #region Collapsible content */ diff --git a/packages/fluent-theme/src/components/theme/Theme.module.css b/packages/fluent-theme/src/components/theme/Theme.module.css index 94e8138f7e..e8b9a3cadc 100644 --- a/packages/fluent-theme/src/components/theme/Theme.module.css +++ b/packages/fluent-theme/src/components/theme/Theme.module.css @@ -241,32 +241,6 @@ :global(.webchat-fluent).theme .render-markdown [data-math-type='block'], :global(.webchat-fluent).theme .view-code-dialog__body, :global(.webchat-fluent).theme :global(.view-code-dialog__code-body) { - /* Edge uses -webkit-scrollbar if scrollbar-* is not set */ - scrollbar-color: unset; - scrollbar-width: unset; - /* Firefox */ - -moz-scrollbar-color: var(--webchat-colorNeutralForeground5) transparent; - -moz-scrollbar-width: thin; - - &::-webkit-scrollbar { - height: var(--webchat-spacingVerticalMNudge); - width: var(--webchat-spacingVerticalMNudge); - visibility: hidden; - } - - &::-webkit-scrollbar-corner { - background: transparent; - } - - &:hover::-webkit-scrollbar { - height: var(--webchat-spacingVerticalMNudge); - width: var(--webchat-spacingVerticalMNudge); - } - - &::-webkit-scrollbar-thumb { - background-color: transparent; - } - &:hover::-webkit-scrollbar-thumb, &:has(> .text-area-input:focus)::-webkit-scrollbar-thumb { background-clip: padding-box; @@ -275,14 +249,6 @@ border-right: 4px solid transparent; border: 3px solid transparent; } - - &:hover::-webkit-scrollbar-thumb:hover { - background-clip: padding-box; - background-color: var(--webchat-colorNeutralForeground5); - border-radius: 10px; - border-right: 2px solid transparent; - border: 1px solid transparent; - } } :global(.webchat-fluent).theme.variant-copilot .transcript-focus-area { @@ -451,165 +417,8 @@ } /* Code block */ -:global(.webchat-fluent).theme .code-block { - border: none; - font-family: var(--webchat-fontFamilyMonospace); - font-size: var(--webchat-fontSizeBase300); - - --webchat__code-block__copy-button--color: var(--webchat-colorNeutralForeground1); - --webchat__code-block__copy-button--background: var(--webchat-colorNeutralBackground3); - - .code-block-copy-button { - background: transparent; - border: none; - color: var(--webchat__code-block__copy-button--color); - height: 20px; - margin-inline-start: var(--webchat-spacingHorizontalS); - padding: 0; - width: 20px; - transition: background-color var(--webchat-durationNormal) var(--webchat-curveDecelerateMid); - - /* Without a border, the button has no visible boundary in forced colors, so draw one as the default theme does. */ - @media (forced-colors: active) { - border: var(--webchat-strokeWidthThin) solid ButtonText; - } - - .code-block-copy-button__icon { - --webchat__component-icon--color: currentColor; - - @media (forced-colors: active) { - --webchat__component-icon--color: ButtonText; - } - } - - &:hover { - background: var(--webchat__code-block__copy-button--background); - color: var(--webchat__code-block__copy-button--color); - } - - &:active { - background: var(--webchat__code-block__copy-button--background); - color: var(--webchat__code-block__copy-button--color); - } - - &:focus { - color: var(--webchat__code-block__copy-button--color); - outline-color: currentColor; - } - } - - &:has(.code-block__body:focus-visible):focus-within { - outline: var(--webchat-strokeWidthThick) solid var(--webchat-colorStrokeFocus2); - outline-offset: calc(var(--webchat-strokeWidthThick) * -1); - - .code-block__body { - outline: none; - } - } - - .code-block__body { - /* TODO: what is the right value? The value below matches "normal" for contents display */ - line-height: 1.435; - } - - &:has(> .code-block__theme--github-dark-default) { - --webchat__background--code-block: var(--codeBlockBackground, var(--webchat-colorGrey8)); - --webchat__code-block__copy-button--background: var( - --codeBlockCopyButtonBackgroundPressed, - var(--webchat-colorGrey14) - ); - --webchat__code-block__copy-button--color: var(--codeBlockCopyButtonForeground, var(--webchat__color--code-block)); - --webchat__color--code-block: var(--codeBlockForeground, var(--webchat-colorGrey98)); - } - - &:has(> .code-block__theme--github-light-default) { - --webchat__background--code-block: var(--codeBlockBackground, var(--webchat-colorGrey98)); - --webchat__code-block__copy-button--background: var(--codeBlockBackgroundPressed, var(--webchat-colorGrey92)); - --webchat__code-block__copy-button--color: var(--codeBlockCopyButtonForeground, var(--webchat__color--code-block)); - --webchat__color--code-block: var(--codeBlockForeground, var(--webchat-colorGrey8)); - } -} /* View Code dialog */ -:global(.webchat-fluent).theme :global(.webchat__modal-dialog).view-code-dialog { - :global(.webchat__modal-dialog__close-button-layout) { - display: flex; - padding: var(--webchat-spacingVerticalS) var(--webchat-spacingHorizontalS); - } - - :global(.webchat__modal-dialog__close-button) { - align-items: center; - aspect-ratio: 1; - display: flex; - height: var(--webchat-lineHeightBase300); - justify-content: center; - padding: 0; - width: unset; - } - - .code-block-copy-button { - --webchat__code-block__copy-button--color: var(--webchat-colorNeutralForeground1); - --webchat__code-block__copy-button--background: var(--webchat-colorNeutralBackground3); - - margin-block-start: var(--webchat-spacingVerticalS); - position: absolute; - right: 32px; - top: 0; - } - - :global(.webchat__modal-dialog__box) { - width: fit-content; - } - - :global(.webchat__modal-dialog__body) { - margin: 0; - padding: var(--webchat-spacingVerticalS) var(--webchat-spacingHorizontalM); - } - - .view-code-dialog__title { - font-size: var(--webchat-fontSizeBase300); - font-weight: var(--webchat-fontWeightSemibold); - line-height: var(--webchat-lineHeightBase300); - } - - .view-code-dialog__body { - font-family: var(--webchat-fontFamilyMonospace); - font-size: var(--webchat-fontSizeBase300); - margin: 0 calc(var(--webchat-spacingHorizontalM) * -1); - padding: 0 var(--webchat-spacingHorizontalM); - } - - .code-block__body { - /* TODO: what is the right value? The value below matches "normal" for block display */ - line-height: normal; - } - - .view-code-dialog__footer { - color: var(--webchat-colorNeutralForeground4); - font-size: var(--webchat-fontSizeBase100); - line-height: var(--webchat-lineHeightBase100); - } - - :global(.view-code-dialog__link) { - color: var(--webchat-colorBrandForegroundLink); - text-decoration-color: transparent; - - &:target { - color: var(--webchat-colorBrandForegroundLinkSelected); - } - &:hover { - color: var(--webchat-colorBrandForegroundLinkHover); - text-decoration: underline 1px currentColor; - } - &:active { - color: var(--webchat-colorBrandForegroundLinkPressed); - } - &:focus-visible { - outline: none; - text-decoration: underline 1px double var(--webchat-colorStrokeFocus2); - } - } -} /* Activity button */ :global(.webchat-fluent).theme .activity-button { diff --git a/packages/fluent-theme/src/overrides/CodeBlock.module.css b/packages/fluent-theme/src/overrides/CodeBlock.module.css new file mode 100644 index 0000000000..160e3a8997 --- /dev/null +++ b/packages/fluent-theme/src/overrides/CodeBlock.module.css @@ -0,0 +1,45 @@ +:global(.webchat-fluent).theme .code-block { + border: none; + font-family: var(--webchat-fontFamilyMonospace); + font-size: var(--webchat-fontSizeBase300); + + --webchat__code-block__copy-button--color: var(--webchat-colorNeutralForeground1); + --webchat__code-block__copy-button--background: var(--webchat-colorNeutralBackground3); + + &:has(.code-block__body:focus-visible):focus-within { + outline: var(--webchat-strokeWidthThick) solid var(--webchat-colorStrokeFocus2); + outline-offset: calc(var(--webchat-strokeWidthThick) * -1); + + .code-block__body { + outline: none; + } + } + + .code-block__body { + /* TODO: what is the right value? The value below matches "normal" for contents display */ + line-height: 1.435; + } + + &:has(> .code-block__theme--github-dark-default) { + --webchat__background--code-block: var(--codeBlockBackground, var(--webchat-colorGrey8)); + --webchat__code-block__copy-button--background: var( + --codeBlockCopyButtonBackgroundPressed, + var(--webchat-colorGrey14) + ); + --webchat__code-block__copy-button--color: var(--codeBlockCopyButtonForeground, var(--webchat__color--code-block)); + --webchat__color--code-block: var(--codeBlockForeground, var(--webchat-colorGrey98)); + } + + &:has(> .code-block__theme--github-light-default) { + --webchat__background--code-block: var(--codeBlockBackground, var(--webchat-colorGrey98)); + --webchat__code-block__copy-button--background: var(--codeBlockBackgroundPressed, var(--webchat-colorGrey92)); + --webchat__code-block__copy-button--color: var(--codeBlockCopyButtonForeground, var(--webchat__color--code-block)); + --webchat__color--code-block: var(--codeBlockForeground, var(--webchat-colorGrey8)); + } +} +:global(.webchat-fluent).theme :global(.webchat__modal-dialog).view-code-dialog { + .code-block__body { + /* TODO: what is the right value? The value below matches "normal" for block display */ + line-height: normal; + } +} diff --git a/packages/fluent-theme/src/overrides/CodeBlockContent.module.css b/packages/fluent-theme/src/overrides/CodeBlockContent.module.css new file mode 100644 index 0000000000..43275aacc4 --- /dev/null +++ b/packages/fluent-theme/src/overrides/CodeBlockContent.module.css @@ -0,0 +1,60 @@ +:global(.webchat-fluent) .part-grouping-decorator { + .stacked-layout .bubble .code-block-content { + border-radius: var(--webchat-borderRadiusXLarge); + border: var(--webchat-strokeWidthThin) solid var(--webchat-colorNeutralStroke1); + font-size: var(--webchat-fontSizeBase300); + font-weight: var(--webchat-fontWeightRegular); + margin-block: var(--webchat-spacingVerticalNone); + margin-inline: var(--webchat-part-grouping__code-block--space-inline); + + .code-block-content__header { + padding: var(--webchat-spacingVerticalM) var(--webchat-spacingHorizontalL); + } + + .code-block-content__code-block { + padding-block: var(--webchat-spacingVerticalM); + padding-inline: var(--webchat-spacingHorizontalL) var(--webchat-spacingHorizontalS); + } + } +} +:global(.webchat-fluent).theme .code-block-content .code-block-content__code-block, +:global(.webchat-fluent).theme .send-box-attachment-bar.send-box-attachment-bar--as-list-item, +:global(.webchat-fluent).theme .text-area.text-area--scroll, +:global(.webchat-fluent).theme :global(.webchat__basic-transcript .webchat__basic-transcript__scrollable), +:global(.webchat-fluent).theme .render-markdown [data-math-type='block'], +:global(.webchat-fluent).theme .view-code-dialog__body, +:global(.webchat-fluent).theme :global(.view-code-dialog__code-body) { + /* Edge uses -webkit-scrollbar if scrollbar-* is not set */ + scrollbar-color: unset; + scrollbar-width: unset; + /* Firefox */ + -moz-scrollbar-color: var(--webchat-colorNeutralForeground5) transparent; + -moz-scrollbar-width: thin; + + &::-webkit-scrollbar { + height: var(--webchat-spacingVerticalMNudge); + width: var(--webchat-spacingVerticalMNudge); + visibility: hidden; + } + + &::-webkit-scrollbar-corner { + background: transparent; + } + + &:hover::-webkit-scrollbar { + height: var(--webchat-spacingVerticalMNudge); + width: var(--webchat-spacingVerticalMNudge); + } + + &::-webkit-scrollbar-thumb { + background-color: transparent; + } + + &:hover::-webkit-scrollbar-thumb:hover { + background-clip: padding-box; + background-color: var(--webchat-colorNeutralForeground5); + border-radius: 10px; + border-right: 2px solid transparent; + border: 1px solid transparent; + } +} diff --git a/packages/fluent-theme/src/overrides/CodeBlockCopyButton.module.css b/packages/fluent-theme/src/overrides/CodeBlockCopyButton.module.css new file mode 100644 index 0000000000..1845975973 --- /dev/null +++ b/packages/fluent-theme/src/overrides/CodeBlockCopyButton.module.css @@ -0,0 +1,65 @@ +:global(.webchat-fluent) .part-grouping-decorator { + .stacked-layout .bubble .code-block-content { + .code-block-copy-button { + --webchat__code-block__copy-button--color: var(--webchat-colorNeutralForeground1); + --webchat__code-block__copy-button--background: var(--webchat-colorNeutralBackground3); + + margin-block-start: var(--webchat-spacingVerticalM); + margin-inline-end: var(--webchat-spacingHorizontalL); + position: absolute; + right: 0; + top: 0; + } + } +} +:global(.webchat-fluent).theme .code-block { + .code-block-copy-button { + background: transparent; + border: none; + color: var(--webchat__code-block__copy-button--color); + height: 20px; + margin-inline-start: var(--webchat-spacingHorizontalS); + padding: 0; + width: 20px; + transition: background-color var(--webchat-durationNormal) var(--webchat-curveDecelerateMid); + + /* Without a border, the button has no visible boundary in forced colors, so draw one as the default theme does. */ + @media (forced-colors: active) { + border: var(--webchat-strokeWidthThin) solid ButtonText; + } + + .code-block-copy-button__icon { + --webchat__component-icon--color: currentColor; + + @media (forced-colors: active) { + --webchat__component-icon--color: ButtonText; + } + } + + &:hover { + background: var(--webchat__code-block__copy-button--background); + color: var(--webchat__code-block__copy-button--color); + } + + &:active { + background: var(--webchat__code-block__copy-button--background); + color: var(--webchat__code-block__copy-button--color); + } + + &:focus { + color: var(--webchat__code-block__copy-button--color); + outline-color: currentColor; + } + } +} +:global(.webchat-fluent).theme :global(.webchat__modal-dialog).view-code-dialog { + .code-block-copy-button { + --webchat__code-block__copy-button--color: var(--webchat-colorNeutralForeground1); + --webchat__code-block__copy-button--background: var(--webchat-colorNeutralBackground3); + + margin-block-start: var(--webchat-spacingVerticalS); + position: absolute; + right: 32px; + top: 0; + } +} diff --git a/packages/fluent-theme/src/overrides/ViewCodeDialog.module.css b/packages/fluent-theme/src/overrides/ViewCodeDialog.module.css new file mode 100644 index 0000000000..37baaf8c99 --- /dev/null +++ b/packages/fluent-theme/src/overrides/ViewCodeDialog.module.css @@ -0,0 +1,64 @@ +:global(.webchat-fluent).theme :global(.webchat__modal-dialog).view-code-dialog { + :global(.webchat__modal-dialog__close-button-layout) { + display: flex; + padding: var(--webchat-spacingVerticalS) var(--webchat-spacingHorizontalS); + } + + :global(.webchat__modal-dialog__close-button) { + align-items: center; + aspect-ratio: 1; + display: flex; + height: var(--webchat-lineHeightBase300); + justify-content: center; + padding: 0; + width: unset; + } + + :global(.webchat__modal-dialog__box) { + width: fit-content; + } + + :global(.webchat__modal-dialog__body) { + margin: 0; + padding: var(--webchat-spacingVerticalS) var(--webchat-spacingHorizontalM); + } + + .view-code-dialog__title { + font-size: var(--webchat-fontSizeBase300); + font-weight: var(--webchat-fontWeightSemibold); + line-height: var(--webchat-lineHeightBase300); + } + + .view-code-dialog__body { + font-family: var(--webchat-fontFamilyMonospace); + font-size: var(--webchat-fontSizeBase300); + margin: 0 calc(var(--webchat-spacingHorizontalM) * -1); + padding: 0 var(--webchat-spacingHorizontalM); + } + + .view-code-dialog__footer { + color: var(--webchat-colorNeutralForeground4); + font-size: var(--webchat-fontSizeBase100); + line-height: var(--webchat-lineHeightBase100); + } + + :global(.view-code-dialog__link) { + color: var(--webchat-colorBrandForegroundLink); + text-decoration-color: transparent; + + &:target { + color: var(--webchat-colorBrandForegroundLinkSelected); + } + &:hover { + color: var(--webchat-colorBrandForegroundLinkHover); + text-decoration: underline 1px currentColor; + } + &:active { + color: var(--webchat-colorBrandForegroundLinkPressed); + } + &:focus-visible { + outline: none; + text-decoration: underline 1px double var(--webchat-colorStrokeFocus2); + } + } +} diff --git a/packages/fluent-theme/src/private/fluentCSSModuleOverrides.ts b/packages/fluent-theme/src/private/fluentCSSModuleOverrides.ts index 2d39064890..317503176e 100644 --- a/packages/fluent-theme/src/private/fluentCSSModuleOverrides.ts +++ b/packages/fluent-theme/src/private/fluentCSSModuleOverrides.ts @@ -17,10 +17,6 @@ const partGroupingDecoratorOverrides: readonly string[] = Object.freeze([ 'carousel-filmstrip__alignment-pad', 'carousel-filmstrip__avatar-gutter', 'carousel-layout', - 'code-block-content', - 'code-block-content__code-block', - 'code-block-content__header', - 'code-block-copy-button', 'collapsible-content', 'collapsible-content__chevron', 'collapsible-content__content', @@ -90,14 +86,8 @@ const themeOverrides: readonly string[] = Object.freeze([ 'bubble', 'bubble__content', 'bubble__nub-pad', - 'code-block', 'code-block-content', 'code-block-content__code-block', - 'code-block-copy-button', - 'code-block-copy-button__icon', - 'code-block__body', - 'code-block__theme--github-dark-default', - 'code-block__theme--github-light-default', 'collapsible-grouping', 'render-markdown', 'send-box-attachment-bar', @@ -110,10 +100,7 @@ const themeOverrides: readonly string[] = Object.freeze([ 'transcript-focus-area__content-overlay', 'transcript-focus-area__indicator', 'transcript-focus-area__transcript-indicator', - 'view-code-dialog', - 'view-code-dialog__body', - 'view-code-dialog__footer', - 'view-code-dialog__title' + 'view-code-dialog__body' ]); export { partGroupingDecoratorOverrides, sendBoxOverrides, themeOverrides }; diff --git a/packages/fluent-theme/src/private/fluentCSSModuleTheme.ts b/packages/fluent-theme/src/private/fluentCSSModuleTheme.ts index 763b02e62c..5182dc5b35 100644 --- a/packages/fluent-theme/src/private/fluentCSSModuleTheme.ts +++ b/packages/fluent-theme/src/private/fluentCSSModuleTheme.ts @@ -3,6 +3,10 @@ import { combineCSSModules } from 'botframework-webchat/internal.js'; import partGroupingDecoratorStyles from '../components/activity/PartGroupingDecorator.module.css'; import sendBoxStyles from '../components/sendBox/SendBox.module.css'; import themeStyles from '../components/theme/Theme.module.css'; +import codeBlockContentOverrideStyles from '../overrides/CodeBlockContent.module.css'; +import codeBlockCopyButtonOverrideStyles from '../overrides/CodeBlockCopyButton.module.css'; +import codeBlockOverrideStyles from '../overrides/CodeBlock.module.css'; +import viewCodeDialogOverrideStyles from '../overrides/ViewCodeDialog.module.css'; import thumbButtonOverrideStyles from '../overrides/ThumbButton.module.css'; import feedbackFormOverrideStyles from '../overrides/FeedbackForm.module.css'; import { partGroupingDecoratorOverrides, sendBoxOverrides, themeOverrides } from './fluentCSSModuleOverrides'; @@ -15,6 +19,10 @@ const fluentCSSModuleTheme = combineCSSModules( pickCSSModule(partGroupingDecoratorStyles, partGroupingDecoratorOverrides), pickCSSModule(sendBoxStyles, sendBoxOverrides), pickCSSModule(themeStyles, themeOverrides), + codeBlockContentOverrideStyles, + codeBlockCopyButtonOverrideStyles, + codeBlockOverrideStyles, + viewCodeDialogOverrideStyles, thumbButtonOverrideStyles, feedbackFormOverrideStyles ); From 66028d09278bdf6e490b2f8c9864b5ef54f52a8b Mon Sep 17 00:00:00 2001 From: Eugene Olonov Date: Fri, 30 Oct 2026 17:49:37 -0700 Subject: [PATCH 13/24] Move Fluent's rules for the send box to override modules Rules move verbatim from SendBox.module.css and Theme.module.css to overrides/AttachmentBar.module.css, AttachmentBarItem.module.css, FilePreview.module.css and TextArea.module.css. --- .../src/components/sendBox/SendBox.module.css | 61 +------------------ .../src/components/theme/Theme.module.css | 18 ------ .../src/overrides/AttachmentBar.module.css | 13 ++++ .../overrides/AttachmentBarItem.module.css | 44 +++++++++++++ .../src/overrides/FilePreview.module.css | 5 ++ .../src/overrides/TextArea.module.css | 17 ++++++ .../src/private/fluentCSSModuleOverrides.ts | 24 +------- .../src/private/fluentCSSModuleTheme.ts | 12 +++- 8 files changed, 92 insertions(+), 102 deletions(-) create mode 100644 packages/fluent-theme/src/overrides/AttachmentBar.module.css create mode 100644 packages/fluent-theme/src/overrides/AttachmentBarItem.module.css create mode 100644 packages/fluent-theme/src/overrides/FilePreview.module.css create mode 100644 packages/fluent-theme/src/overrides/TextArea.module.css diff --git a/packages/fluent-theme/src/components/sendBox/SendBox.module.css b/packages/fluent-theme/src/components/sendBox/SendBox.module.css index c07dbc7b5b..e12cbb7403 100644 --- a/packages/fluent-theme/src/components/sendBox/SendBox.module.css +++ b/packages/fluent-theme/src/components/sendBox/SendBox.module.css @@ -77,65 +77,8 @@ } } -:global(.webchat-fluent) .sendbox__attachment-bar.send-box-attachment-bar { - .send-box-attachment-bar__box { - gap: var(--webchat-spacingVerticalXS) var(--webchat-spacingHorizontalXS); - } - - &.send-box-attachment-bar--as-list-item { - margin-inline-end: calc(-1 * var(--webchat-spacingHorizontalS)); - } - - &.send-box-attachment-bar--as-list-item .send-box-attachment-bar__box:not(:empty) { - padding: var(--webchat-spacingVerticalXS) var(--webchat-spacingHorizontalXS); - } - - .send-box-attachment-bar-item.send-box-attachment-bar-item--as-list-item { - box-shadow: var(--webchat-shadow2); - border-radius: var(--webchat-borderRadiusMedium); - padding: var(--webchat-spacingVerticalXXS) var(--webchat-spacingHorizontalXXS); - } - - .send-box-attachment-bar-item.send-box-attachment-bar-item--as-thumbnail { - border: var(--webchat-strokeWidthThin) solid var(--webchat-colorNeutralStroke1Selected); - border-radius: var(--webchat-borderRadiusXLarge); - } - - .send-box-attachment-bar-item .send-box-attachment-bar-item__delete-button { - background-color: var(--webchat-colorNeutralBackground1); - border-radius: var(--webchat-borderRadiusMedium); - color: var(--webchat-colorNeutralForeground1); - transition: - background-color var(--webchat-durationNormal) var(--webchat-curveAccelerateMid), - color var(--webchat-durationNormal) var(--webchat-curveAccelerateMid), - opacity var(--webchat-durationUltraFast) var(--webchat-curveAccelerateMid); - - &:hover { - background-color: var(--webchat-colorNeutralBackground1Hover); - color: var(--webchat-colorNeutralForeground1Hover); - } - - &:active { - background-color: var(--webchat-colorNeutralBackground1Pressed); - color: var(--webchat-colorNeutralForeground1Pressed); - } - - &:disabled, - &[aria-disabled='true'] { - background-color: var(--webchat-colorNeutralBackgroundDisabled); - border-color: var(--webchat-colorNeutralStrokeDisabled); - color: var(--webchat-colorNeutralForegroundDisabled); - } - } - - .send-box-attachment-bar-item.send-box-attachment-bar-item.send-box-attachment-bar-item.send-box-attachment-bar-item--as-list-item - .send-box-attachment-bar-item__preview { - padding-inline: var(--webchat-spacingHorizontalS); - } - - .send-box-attachment-bar-item-file-preview.send-box-attachment-bar-item-file-preview--as-list-item { - gap: var(--webchat-spacingVerticalS) var(--webchat-spacingHorizontalS); - } +.sendbox__attachment-bar { + --webchat-preserve-reason: target-class; } :global(.webchat-fluent) .sendbox__sendbox-text-area { diff --git a/packages/fluent-theme/src/components/theme/Theme.module.css b/packages/fluent-theme/src/components/theme/Theme.module.css index e8b9a3cadc..42133e1f1c 100644 --- a/packages/fluent-theme/src/components/theme/Theme.module.css +++ b/packages/fluent-theme/src/components/theme/Theme.module.css @@ -233,24 +233,6 @@ padding-inline-start: var(--webchat-spacingHorizontalMNudge); } -/* Scrollbars */ -:global(.webchat-fluent).theme .code-block-content .code-block-content__code-block, -:global(.webchat-fluent).theme .send-box-attachment-bar.send-box-attachment-bar--as-list-item, -:global(.webchat-fluent).theme .text-area.text-area--scroll, -:global(.webchat-fluent).theme :global(.webchat__basic-transcript .webchat__basic-transcript__scrollable), -:global(.webchat-fluent).theme .render-markdown [data-math-type='block'], -:global(.webchat-fluent).theme .view-code-dialog__body, -:global(.webchat-fluent).theme :global(.view-code-dialog__code-body) { - &:hover::-webkit-scrollbar-thumb, - &:has(> .text-area-input:focus)::-webkit-scrollbar-thumb { - background-clip: padding-box; - background-color: var(--webchat-colorNeutralForeground5); - border-radius: 10px; - border-right: 4px solid transparent; - border: 3px solid transparent; - } -} - :global(.webchat-fluent).theme.variant-copilot .transcript-focus-area { --webchat__transcript--spacing: var(--webchat-spacingVerticalM); } diff --git a/packages/fluent-theme/src/overrides/AttachmentBar.module.css b/packages/fluent-theme/src/overrides/AttachmentBar.module.css new file mode 100644 index 0000000000..8023ff3df7 --- /dev/null +++ b/packages/fluent-theme/src/overrides/AttachmentBar.module.css @@ -0,0 +1,13 @@ +:global(.webchat-fluent) .sendbox__attachment-bar.send-box-attachment-bar { + .send-box-attachment-bar__box { + gap: var(--webchat-spacingVerticalXS) var(--webchat-spacingHorizontalXS); + } + + &.send-box-attachment-bar--as-list-item { + margin-inline-end: calc(-1 * var(--webchat-spacingHorizontalS)); + } + + &.send-box-attachment-bar--as-list-item .send-box-attachment-bar__box:not(:empty) { + padding: var(--webchat-spacingVerticalXS) var(--webchat-spacingHorizontalXS); + } +} diff --git a/packages/fluent-theme/src/overrides/AttachmentBarItem.module.css b/packages/fluent-theme/src/overrides/AttachmentBarItem.module.css new file mode 100644 index 0000000000..70303fbccc --- /dev/null +++ b/packages/fluent-theme/src/overrides/AttachmentBarItem.module.css @@ -0,0 +1,44 @@ +:global(.webchat-fluent) .sendbox__attachment-bar.send-box-attachment-bar { + .send-box-attachment-bar-item.send-box-attachment-bar-item--as-list-item { + box-shadow: var(--webchat-shadow2); + border-radius: var(--webchat-borderRadiusMedium); + padding: var(--webchat-spacingVerticalXXS) var(--webchat-spacingHorizontalXXS); + } + + .send-box-attachment-bar-item.send-box-attachment-bar-item--as-thumbnail { + border: var(--webchat-strokeWidthThin) solid var(--webchat-colorNeutralStroke1Selected); + border-radius: var(--webchat-borderRadiusXLarge); + } + + .send-box-attachment-bar-item .send-box-attachment-bar-item__delete-button { + background-color: var(--webchat-colorNeutralBackground1); + border-radius: var(--webchat-borderRadiusMedium); + color: var(--webchat-colorNeutralForeground1); + transition: + background-color var(--webchat-durationNormal) var(--webchat-curveAccelerateMid), + color var(--webchat-durationNormal) var(--webchat-curveAccelerateMid), + opacity var(--webchat-durationUltraFast) var(--webchat-curveAccelerateMid); + + &:hover { + background-color: var(--webchat-colorNeutralBackground1Hover); + color: var(--webchat-colorNeutralForeground1Hover); + } + + &:active { + background-color: var(--webchat-colorNeutralBackground1Pressed); + color: var(--webchat-colorNeutralForeground1Pressed); + } + + &:disabled, + &[aria-disabled='true'] { + background-color: var(--webchat-colorNeutralBackgroundDisabled); + border-color: var(--webchat-colorNeutralStrokeDisabled); + color: var(--webchat-colorNeutralForegroundDisabled); + } + } + + .send-box-attachment-bar-item.send-box-attachment-bar-item.send-box-attachment-bar-item.send-box-attachment-bar-item--as-list-item + .send-box-attachment-bar-item__preview { + padding-inline: var(--webchat-spacingHorizontalS); + } +} diff --git a/packages/fluent-theme/src/overrides/FilePreview.module.css b/packages/fluent-theme/src/overrides/FilePreview.module.css new file mode 100644 index 0000000000..eaef762617 --- /dev/null +++ b/packages/fluent-theme/src/overrides/FilePreview.module.css @@ -0,0 +1,5 @@ +:global(.webchat-fluent) .sendbox__attachment-bar.send-box-attachment-bar { + .send-box-attachment-bar-item-file-preview.send-box-attachment-bar-item-file-preview--as-list-item { + gap: var(--webchat-spacingVerticalS) var(--webchat-spacingHorizontalS); + } +} diff --git a/packages/fluent-theme/src/overrides/TextArea.module.css b/packages/fluent-theme/src/overrides/TextArea.module.css new file mode 100644 index 0000000000..3d6c405296 --- /dev/null +++ b/packages/fluent-theme/src/overrides/TextArea.module.css @@ -0,0 +1,17 @@ +/* Scrollbars */ +:global(.webchat-fluent).theme .code-block-content .code-block-content__code-block, +:global(.webchat-fluent).theme .send-box-attachment-bar.send-box-attachment-bar--as-list-item, +:global(.webchat-fluent).theme .text-area.text-area--scroll, +:global(.webchat-fluent).theme :global(.webchat__basic-transcript .webchat__basic-transcript__scrollable), +:global(.webchat-fluent).theme .render-markdown [data-math-type='block'], +:global(.webchat-fluent).theme .view-code-dialog__body, +:global(.webchat-fluent).theme :global(.view-code-dialog__code-body) { + &:hover::-webkit-scrollbar-thumb, + &:has(> .text-area-input:focus)::-webkit-scrollbar-thumb { + background-clip: padding-box; + background-color: var(--webchat-colorNeutralForeground5); + border-radius: 10px; + border-right: 4px solid transparent; + border: 3px solid transparent; + } +} diff --git a/packages/fluent-theme/src/private/fluentCSSModuleOverrides.ts b/packages/fluent-theme/src/private/fluentCSSModuleOverrides.ts index 317503176e..fdc30ac9ae 100644 --- a/packages/fluent-theme/src/private/fluentCSSModuleOverrides.ts +++ b/packages/fluent-theme/src/private/fluentCSSModuleOverrides.ts @@ -65,18 +65,7 @@ const partGroupingDecoratorOverrides: readonly string[] = Object.freeze([ ]); // SendBox.module.css -const sendBoxOverrides: readonly string[] = Object.freeze([ - 'send-box-attachment-bar', - 'send-box-attachment-bar--as-list-item', - 'send-box-attachment-bar-item', - 'send-box-attachment-bar-item--as-list-item', - 'send-box-attachment-bar-item--as-thumbnail', - 'send-box-attachment-bar-item-file-preview', - 'send-box-attachment-bar-item-file-preview--as-list-item', - 'send-box-attachment-bar-item__delete-button', - 'send-box-attachment-bar-item__preview', - 'send-box-attachment-bar__box' -]); +const sendBoxOverrides: readonly string[] = Object.freeze([]); // Theme.module.css const themeOverrides: readonly string[] = Object.freeze([ @@ -86,21 +75,12 @@ const themeOverrides: readonly string[] = Object.freeze([ 'bubble', 'bubble__content', 'bubble__nub-pad', - 'code-block-content', - 'code-block-content__code-block', 'collapsible-grouping', - 'render-markdown', - 'send-box-attachment-bar', - 'send-box-attachment-bar--as-list-item', - 'text-area', - 'text-area--scroll', - 'text-area-input', 'transcript-focus-area', 'transcript-focus-area__content--focused', 'transcript-focus-area__content-overlay', 'transcript-focus-area__indicator', - 'transcript-focus-area__transcript-indicator', - 'view-code-dialog__body' + 'transcript-focus-area__transcript-indicator' ]); export { partGroupingDecoratorOverrides, sendBoxOverrides, themeOverrides }; diff --git a/packages/fluent-theme/src/private/fluentCSSModuleTheme.ts b/packages/fluent-theme/src/private/fluentCSSModuleTheme.ts index 5182dc5b35..5c11c20962 100644 --- a/packages/fluent-theme/src/private/fluentCSSModuleTheme.ts +++ b/packages/fluent-theme/src/private/fluentCSSModuleTheme.ts @@ -1,15 +1,18 @@ import { combineCSSModules } from 'botframework-webchat/internal.js'; import partGroupingDecoratorStyles from '../components/activity/PartGroupingDecorator.module.css'; -import sendBoxStyles from '../components/sendBox/SendBox.module.css'; import themeStyles from '../components/theme/Theme.module.css'; import codeBlockContentOverrideStyles from '../overrides/CodeBlockContent.module.css'; import codeBlockCopyButtonOverrideStyles from '../overrides/CodeBlockCopyButton.module.css'; +import attachmentBarOverrideStyles from '../overrides/AttachmentBar.module.css'; +import attachmentBarItemOverrideStyles from '../overrides/AttachmentBarItem.module.css'; +import filePreviewOverrideStyles from '../overrides/FilePreview.module.css'; +import textAreaOverrideStyles from '../overrides/TextArea.module.css'; import codeBlockOverrideStyles from '../overrides/CodeBlock.module.css'; import viewCodeDialogOverrideStyles from '../overrides/ViewCodeDialog.module.css'; import thumbButtonOverrideStyles from '../overrides/ThumbButton.module.css'; import feedbackFormOverrideStyles from '../overrides/FeedbackForm.module.css'; -import { partGroupingDecoratorOverrides, sendBoxOverrides, themeOverrides } from './fluentCSSModuleOverrides'; +import { partGroupingDecoratorOverrides, themeOverrides } from './fluentCSSModuleOverrides'; import pickCSSModule from './pickCSSModule'; // Fluent's class names for the Web Chat elements it styles, for in FluentThemeProvider. @@ -17,10 +20,13 @@ import pickCSSModule from './pickCSSModule'; // Fluent's CSS, to keep the cascade. Built once, so every useStyles() below the composer gets a stable theme. const fluentCSSModuleTheme = combineCSSModules( pickCSSModule(partGroupingDecoratorStyles, partGroupingDecoratorOverrides), - pickCSSModule(sendBoxStyles, sendBoxOverrides), pickCSSModule(themeStyles, themeOverrides), codeBlockContentOverrideStyles, codeBlockCopyButtonOverrideStyles, + attachmentBarOverrideStyles, + attachmentBarItemOverrideStyles, + filePreviewOverrideStyles, + textAreaOverrideStyles, codeBlockOverrideStyles, viewCodeDialogOverrideStyles, thumbButtonOverrideStyles, From 4c7366a480589240f80886045c593de68b0cef4f Mon Sep 17 00:00:00 2001 From: Eugene Olonov Date: Fri, 30 Oct 2026 17:49:43 -0700 Subject: [PATCH 14/24] Move Fluent's rules for the transcript focus to an override module Rules move verbatim from Theme.module.css to overrides/TranscriptFocus.module.css. --- .../activity/PartGroupingDecorator.module.css | 11 --- .../src/components/theme/Theme.module.css | 53 --------------- .../src/overrides/TranscriptFocus.module.css | 68 +++++++++++++++++++ .../src/private/fluentCSSModuleOverrides.ts | 8 +-- .../src/private/fluentCSSModuleTheme.ts | 2 + 5 files changed, 72 insertions(+), 70 deletions(-) create mode 100644 packages/fluent-theme/src/overrides/TranscriptFocus.module.css diff --git a/packages/fluent-theme/src/components/activity/PartGroupingDecorator.module.css b/packages/fluent-theme/src/components/activity/PartGroupingDecorator.module.css index 8179d4d422..4d5b05f09e 100644 --- a/packages/fluent-theme/src/components/activity/PartGroupingDecorator.module.css +++ b/packages/fluent-theme/src/components/activity/PartGroupingDecorator.module.css @@ -560,12 +560,6 @@ &.variant-fluent { --webchat-part-grouping__bubble--box-shadow: var(--webchat-shadow4); - .stacked-layout .transcript-focus-area__activity-list { - --webchat--collapsible-grouping__list--gap: var(--webchat-spacingVerticalXS); - padding: var(--webchat-spacingVerticalNone) var(--webchat-spacingHorizontalNone); - gap: var(--webchat-spacingVerticalXS); - } - .stacked-layout .stacked-layout__attachment-row { margin-block-start: var(--webchat-spacingVerticalMNudge); } @@ -941,11 +935,6 @@ } } - .stacked-layout .transcript-focus-area__activity-list { - --webchat--collapsible-grouping__list--gap: var(--webchat-spacingVerticalS); - padding: var(--webchat-spacingVerticalM) var(--webchat-spacingHorizontalM); - } - .border-flair { border-radius: var(--webchat-borderRadius2XLarge); inset: anchor(top) anchor(right) anchor(bottom) anchor(left); diff --git a/packages/fluent-theme/src/components/theme/Theme.module.css b/packages/fluent-theme/src/components/theme/Theme.module.css index 42133e1f1c..acb831509c 100644 --- a/packages/fluent-theme/src/components/theme/Theme.module.css +++ b/packages/fluent-theme/src/components/theme/Theme.module.css @@ -233,41 +233,13 @@ padding-inline-start: var(--webchat-spacingHorizontalMNudge); } -:global(.webchat-fluent).theme.variant-copilot .transcript-focus-area { - --webchat__transcript--spacing: var(--webchat-spacingVerticalM); -} - :global(.webchat-fluent).theme.variant-copilot :global(.webchat__basic-transcript .webchat__basic-transcript__scrollable) { scrollbar-gutter: stable; } -/* Transcript focus indicator color */ -:global(.webchat-fluent).theme .transcript-focus-area__transcript-indicator { - --webchat__color--transcript-visual-keyboard-indicator: var(--webchat-colorStrokeFocus2); -} - -/* Transcript Content indicator without focus-visible state */ -:global(.webchat-fluent).theme .transcript-focus-area:not(:focus-visible) .transcript-focus-area__indicator { - /* Hide activity focus when no focus-visible state */ - display: none; -} - -/* Transcript Focus indicator when content focused */ -:global(.webchat-fluent).theme .transcript-focus-area:focus-visible:has(.transcript-focus-area__content--focused) { - .transcript-focus-area__transcript-indicator { - /* Hide transcript focus when an activity is focused */ - display: none; - } -} - /* Transcript focused content */ :global(.webchat-fluent).theme .transcript-focus-area:focus-visible .transcript-focus-area__content--focused { - .transcript-focus-area__indicator { - /* Hide transcript activity focus when the content is focused */ - display: none; - } - /* Transcript activity focused directly */ > :global(.webchat__focus-trap) > :global(.webchat__basic-transcript__activity-body) .bubble { display: grid; @@ -287,10 +259,6 @@ } } - &:has(.collapsible-grouping) > .transcript-focus-area__content-overlay .transcript-focus-area__indicator { - display: none; - } - .collapsible-grouping::after { border-radius: var(--webchat-part-grouping__bubble--border-radius); border: none; @@ -305,27 +273,6 @@ :global(.activity-decorator) .bubble .bubble__nub-pad { display: none; } - - :global(.pre-chat-message-activity)::after { - border-radius: var(--webchat-part-grouping__bubble--border-radius); - content: ''; - inset: 0; - outline-offset: 0; - outline: var(--webchat-strokeWidthThick) solid var(--webchat-colorStrokeFocus2); - pointer-events: none; - position: absolute; - } - - :global(.pre-chat-message-activity)::after { - border-radius: var(--webchat-borderRadiusMedium); - outline-offset: -3px; - } - - :global(.liner-message-activity__text) { - border-radius: 20px; - outline-offset: 4px; - outline: var(--webchat-strokeWidthThick) solid var(--webchat-colorStrokeFocus2); - } } /* Transcript focused content copilot variant */ diff --git a/packages/fluent-theme/src/overrides/TranscriptFocus.module.css b/packages/fluent-theme/src/overrides/TranscriptFocus.module.css new file mode 100644 index 0000000000..be73802ea2 --- /dev/null +++ b/packages/fluent-theme/src/overrides/TranscriptFocus.module.css @@ -0,0 +1,68 @@ +:global(.webchat-fluent) .part-grouping-decorator { + &.variant-fluent { + .stacked-layout .transcript-focus-area__activity-list { + --webchat--collapsible-grouping__list--gap: var(--webchat-spacingVerticalXS); + padding: var(--webchat-spacingVerticalNone) var(--webchat-spacingHorizontalNone); + gap: var(--webchat-spacingVerticalXS); + } + } + &.variant-copilot { + .stacked-layout .transcript-focus-area__activity-list { + --webchat--collapsible-grouping__list--gap: var(--webchat-spacingVerticalS); + padding: var(--webchat-spacingVerticalM) var(--webchat-spacingHorizontalM); + } + } +} +:global(.webchat-fluent).theme.variant-copilot .transcript-focus-area { + --webchat__transcript--spacing: var(--webchat-spacingVerticalM); +} + +/* Transcript focus indicator color */ +:global(.webchat-fluent).theme .transcript-focus-area__transcript-indicator { + --webchat__color--transcript-visual-keyboard-indicator: var(--webchat-colorStrokeFocus2); +} + +/* Transcript Content indicator without focus-visible state */ +:global(.webchat-fluent).theme .transcript-focus-area:not(:focus-visible) .transcript-focus-area__indicator { + /* Hide activity focus when no focus-visible state */ + display: none; +} + +/* Transcript Focus indicator when content focused */ +:global(.webchat-fluent).theme .transcript-focus-area:focus-visible:has(.transcript-focus-area__content--focused) { + .transcript-focus-area__transcript-indicator { + /* Hide transcript focus when an activity is focused */ + display: none; + } +} +:global(.webchat-fluent).theme .transcript-focus-area:focus-visible .transcript-focus-area__content--focused { + .transcript-focus-area__indicator { + /* Hide transcript activity focus when the content is focused */ + display: none; + } + + &:has(.collapsible-grouping) > .transcript-focus-area__content-overlay .transcript-focus-area__indicator { + display: none; + } + + :global(.pre-chat-message-activity)::after { + border-radius: var(--webchat-part-grouping__bubble--border-radius); + content: ''; + inset: 0; + outline-offset: 0; + outline: var(--webchat-strokeWidthThick) solid var(--webchat-colorStrokeFocus2); + pointer-events: none; + position: absolute; + } + + :global(.pre-chat-message-activity)::after { + border-radius: var(--webchat-borderRadiusMedium); + outline-offset: -3px; + } + + :global(.liner-message-activity__text) { + border-radius: 20px; + outline-offset: 4px; + outline: var(--webchat-strokeWidthThick) solid var(--webchat-colorStrokeFocus2); + } +} diff --git a/packages/fluent-theme/src/private/fluentCSSModuleOverrides.ts b/packages/fluent-theme/src/private/fluentCSSModuleOverrides.ts index fdc30ac9ae..198547fabe 100644 --- a/packages/fluent-theme/src/private/fluentCSSModuleOverrides.ts +++ b/packages/fluent-theme/src/private/fluentCSSModuleOverrides.ts @@ -60,8 +60,7 @@ const partGroupingDecoratorOverrides: readonly string[] = Object.freeze([ 'stacked-layout__status', 'stacked-layout__title', 'text-content', - 'text-content__generated-badge', - 'transcript-focus-area__activity-list' + 'text-content__generated-badge' ]); // SendBox.module.css @@ -77,10 +76,7 @@ const themeOverrides: readonly string[] = Object.freeze([ 'bubble__nub-pad', 'collapsible-grouping', 'transcript-focus-area', - 'transcript-focus-area__content--focused', - 'transcript-focus-area__content-overlay', - 'transcript-focus-area__indicator', - 'transcript-focus-area__transcript-indicator' + 'transcript-focus-area__content--focused' ]); export { partGroupingDecoratorOverrides, sendBoxOverrides, themeOverrides }; diff --git a/packages/fluent-theme/src/private/fluentCSSModuleTheme.ts b/packages/fluent-theme/src/private/fluentCSSModuleTheme.ts index 5c11c20962..fc7eae700b 100644 --- a/packages/fluent-theme/src/private/fluentCSSModuleTheme.ts +++ b/packages/fluent-theme/src/private/fluentCSSModuleTheme.ts @@ -4,6 +4,7 @@ import partGroupingDecoratorStyles from '../components/activity/PartGroupingDeco import themeStyles from '../components/theme/Theme.module.css'; import codeBlockContentOverrideStyles from '../overrides/CodeBlockContent.module.css'; import codeBlockCopyButtonOverrideStyles from '../overrides/CodeBlockCopyButton.module.css'; +import transcriptFocusOverrideStyles from '../overrides/TranscriptFocus.module.css'; import attachmentBarOverrideStyles from '../overrides/AttachmentBar.module.css'; import attachmentBarItemOverrideStyles from '../overrides/AttachmentBarItem.module.css'; import filePreviewOverrideStyles from '../overrides/FilePreview.module.css'; @@ -23,6 +24,7 @@ const fluentCSSModuleTheme = combineCSSModules( pickCSSModule(themeStyles, themeOverrides), codeBlockContentOverrideStyles, codeBlockCopyButtonOverrideStyles, + transcriptFocusOverrideStyles, attachmentBarOverrideStyles, attachmentBarItemOverrideStyles, filePreviewOverrideStyles, From 4ddeb964c325cbaf861c940de7a723329572169f Mon Sep 17 00:00:00 2001 From: Eugene Olonov Date: Fri, 30 Oct 2026 17:49:49 -0700 Subject: [PATCH 15/24] Move Fluent's rules for activity layout to override modules Rules move verbatim from PartGroupingDecorator.module.css and Theme.module.css to overrides/ActivityStatus.module.css, BorderFlair.module.css, Bubble.module.css, CarouselFilmStrip.module.css, ComponentIcon.module.css, MessageStatusLoader.module.css and StackedLayout.module.css. --- .../activity/PartGroupingDecorator.module.css | 324 ------------------ .../src/components/theme/Theme.module.css | 23 -- .../src/overrides/ActivityStatus.module.css | 7 + .../src/overrides/BorderFlair.module.css | 16 + .../src/overrides/Bubble.module.css | 153 +++++++++ .../overrides/CarouselFilmStrip.module.css | 19 + .../src/overrides/ComponentIcon.module.css | 18 + .../overrides/MessageStatusLoader.module.css | 13 + .../src/overrides/StackedLayout.module.css | 176 ++++++++++ .../src/private/fluentCSSModuleOverrides.ts | 20 -- .../src/private/fluentCSSModuleTheme.ts | 14 + 11 files changed, 416 insertions(+), 367 deletions(-) create mode 100644 packages/fluent-theme/src/overrides/ActivityStatus.module.css create mode 100644 packages/fluent-theme/src/overrides/BorderFlair.module.css create mode 100644 packages/fluent-theme/src/overrides/Bubble.module.css create mode 100644 packages/fluent-theme/src/overrides/CarouselFilmStrip.module.css create mode 100644 packages/fluent-theme/src/overrides/ComponentIcon.module.css create mode 100644 packages/fluent-theme/src/overrides/MessageStatusLoader.module.css create mode 100644 packages/fluent-theme/src/overrides/StackedLayout.module.css diff --git a/packages/fluent-theme/src/components/activity/PartGroupingDecorator.module.css b/packages/fluent-theme/src/components/activity/PartGroupingDecorator.module.css index 4d5b05f09e..aa11244c2f 100644 --- a/packages/fluent-theme/src/components/activity/PartGroupingDecorator.module.css +++ b/packages/fluent-theme/src/components/activity/PartGroupingDecorator.module.css @@ -57,38 +57,6 @@ line-height: var(--webchat-lineHeightBase100); padding-inline: var(--webchat-spacingHorizontalXS); } - - .message-status-loader { - --webchat__component-icon--mask: unset; - --webchat__component-icon--image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22100%22%20height%3D%22100%22%20viewBox%3D%220%200%20100%20100%22%3E%3Cdefs%3E%3ClinearGradient%20id%3D%22a%22%20x1%3D%22-.5%22%20x2%3D%221.5%22%20y1%3D%22.5%22%20y2%3D%22.5%22%20gradientUnits%3D%22objectBoundingBox%22%3E%3Cstop%20offset%3D%220%22%20stop-color%3D%22%23d161aa%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%23e96d80%22%2F%3E%3C%2FlinearGradient%3E%3ClinearGradient%20id%3D%22b%22%20x1%3D%22-.5%22%20x2%3D%221.5%22%20y1%3D%22.5%22%20y2%3D%22.5%22%20gradientUnits%3D%22objectBoundingBox%22%3E%3Cstop%20offset%3D%220%22%20stop-color%3D%22%2342a3a6%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%2373af69%22%2F%3E%3C%2FlinearGradient%3E%3ClinearGradient%20id%3D%22c%22%20x1%3D%22-.5%22%20x2%3D%221.5%22%20y1%3D%22.5%22%20y2%3D%22.5%22%20gradientUnits%3D%22objectBoundingBox%22%3E%3Cstop%20offset%3D%220%22%20stop-color%3D%22%2365a5dd%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%238173ee%22%2F%3E%3C%2FlinearGradient%3E%3C%2Fdefs%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22url(%23a)%22%3E%3Canimate%20attributeName%3D%22r%22%20begin%3D%220s%22%20calcMode%3D%22spline%22%20dur%3D%223.6s%22%20keySplines%3D%220.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%22%20keyTimes%3D%220%3B0.1667%3B0.3333%3B0.5%3B0.6667%3B0.8333%3B1%22%20repeatCount%3D%22indefinite%22%20values%3D%2250%3B0%3B0%3B0%3B0%3B49%3B49%22%2F%3E%3Canimate%20attributeName%3D%22opacity%22%20begin%3D%220s%22%20calcMode%3D%22spline%22%20dur%3D%223.6s%22%20keySplines%3D%220.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%22%20keyTimes%3D%220%3B0.1667%3B0.3333%3B0.5%3B0.6667%3B0.6668%3B1%22%20repeatCount%3D%22indefinite%22%20values%3D%221%3B0%3B0%3B0%3B0%3B1%3B1%22%2F%3E%3Canimate%20attributeName%3D%22cx%22%20begin%3D%220s%22%20calcMode%3D%22spline%22%20dur%3D%223.6s%22%20keySplines%3D%220.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%22%20keyTimes%3D%220%3B0.1667%3B0.3333%3B0.5%3B0.6667%3B0.8333%3B1%22%20repeatCount%3D%22indefinite%22%20values%3D%2250%3B100%3B0%3B0%3B0%3B50%3B50%22%2F%3E%3C%2Fcircle%3E%3Ccircle%20cy%3D%2250%22%20r%3D%220%22%20fill%3D%22url(%23b)%22%20opacity%3D%220%22%3E%3Canimate%20attributeName%3D%22r%22%20begin%3D%220s%22%20calcMode%3D%22spline%22%20dur%3D%223.6s%22%20keySplines%3D%220.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%22%20keyTimes%3D%220%3B0.1667%3B0.3333%3B0.5%3B0.6667%3B0.8333%3B1%22%20repeatCount%3D%22indefinite%22%20values%3D%220%3B49%3B49%3B0%3B0%3B0%3B0%22%2F%3E%3Canimate%20attributeName%3D%22opacity%22%20begin%3D%220s%22%20calcMode%3D%22spline%22%20dur%3D%223.6s%22%20keySplines%3D%220.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%22%20keyTimes%3D%220%3B0.0001%3B0.3333%3B0.5%3B0.6667%3B0.8333%3B1%22%20repeatCount%3D%22indefinite%22%20values%3D%220%3B1%3B1%3B0%3B0%3B0%3B0%22%2F%3E%3Canimate%20attributeName%3D%22cx%22%20begin%3D%220s%22%20calcMode%3D%22spline%22%20dur%3D%223.6s%22%20keySplines%3D%220.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%22%20keyTimes%3D%220%3B0.1667%3B0.3333%3B0.5%3B0.6667%3B0.8333%3B1%22%20repeatCount%3D%22indefinite%22%20values%3D%220%3B50%3B50%3B100%3B0%3B0%3B0%22%2F%3E%3C%2Fcircle%3E%3Ccircle%20cy%3D%2250%22%20r%3D%220%22%20fill%3D%22url(%23c)%22%20opacity%3D%220%22%3E%3Canimate%20attributeName%3D%22r%22%20begin%3D%220s%22%20calcMode%3D%22spline%22%20dur%3D%223.6s%22%20keySplines%3D%220.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%22%20keyTimes%3D%220%3B0.1667%3B0.3333%3B0.5%3B0.6667%3B0.8333%3B1%22%20repeatCount%3D%22indefinite%22%20values%3D%220%3B0%3B0%3B49%3B49%3B0%3B0%22%2F%3E%3Canimate%20attributeName%3D%22opacity%22%20begin%3D%220s%22%20calcMode%3D%22spline%22%20dur%3D%223.6s%22%20keySplines%3D%220.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%22%20keyTimes%3D%220%3B0.1667%3B0.1668%3B0.5%3B0.6667%3B0.8333%3B1%22%20repeatCount%3D%22indefinite%22%20values%3D%220%3B0%3B0%3B1%3B1%3B0%3B0%22%2F%3E%3Canimate%20attributeName%3D%22cx%22%20begin%3D%220s%22%20calcMode%3D%22spline%22%20dur%3D%223.6s%22%20keySplines%3D%220.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%22%20keyTimes%3D%220%3B0.1667%3B0.3333%3B0.5%3B0.6667%3B0.8333%3B1%22%20repeatCount%3D%22indefinite%22%20values%3D%220%3B0%3B0%3B50%3B50%3B100%3B0%22%2F%3E%3C%2Fcircle%3E%3C%2Fsvg%3E'); - - animation: none; - color: transparent; - - @media (prefers-reduced-motion: reduce) { - --webchat__component-icon--image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22100%22%20height%3D%22100%22%20viewBox%3D%220%200%20100%20100%22%3E%3Cdefs%3E%3ClinearGradient%20id%3D%22a%22%20x1%3D%22-.5%22%20x2%3D%221.5%22%20y1%3D%22.5%22%20y2%3D%22.5%22%20gradientUnits%3D%22objectBoundingBox%22%3E%3Cstop%20offset%3D%220%22%20stop-color%3D%22%23d161aa%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%23e96d80%22%2F%3E%3C%2FlinearGradient%3E%3C%2Fdefs%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2249%22%20fill%3D%22url(%23a)%22%2F%3E%3C%2Fsvg%3E'); - } - } - - .stacked-layout .bubble :global(.webchat__fileContent__badge) { - cursor: default; - font-size: var(--webchat-fontSizeBase300); - line-height: var(--webchat-lineHeightBase300); - - :global(.webchat__fileContent__fileName) { - color: var(--webchat-colorBrandForegroundLink); - font-family: inherit; - } - - :global(.webchat__fileContent__size) { - color: var(--webchat-colorNeutralForeground2); - font-family: inherit; - } - } - - .stacked-layout .bubble :global(.webchat__fileContent__downloadIcon) { - color: var(--webchat-colorBrandForegroundLink); - } /* #endregion */ /* #region Markdown & citations */ @@ -363,131 +331,13 @@ } } - .collapsible-grouping__content .stacked-layout .stacked-layout__title, - .collapsible-grouping__title { - color: var(--webchat-colorNeutralForeground2); - font-size: var(--webchat-fontSizeBase300); - font-weight: var(--webchat-fontWeightSemibold); - line-height: var(--webchat-lineHeightBase300); - } - .collapsible-grouping, .collapsible-grouping__header { border: none; } - .stacked-layout .stacked-layout__message-status { - --webchat__component-icon--size: 14px; - - color: var(--webchat-colorNeutralForeground2); - font-size: var(--webchat-fontSizeBase400); - } - .collapsible-grouping { max-width: 100%; - - .stacked-layout__message-status { - height: unset; - width: unset; - - .component-icon { - height: 19px; - width: 19px; - } - } - - .stacked-layout :global(.activity-loader) { - display: none; - } - - :global(.part-grouping-decorator) .bubble .bubble__content { - box-sizing: content-box; - gap: var(--webchat-spacingVerticalXS); - } - } - /* #endregion */ - - /* #region Stacked layout & message status */ - .stacked-layout { - display: flex; - flex-flow: column nowrap; - margin-inline: var(--webchat-spacingHorizontalMNudge); - - .stacked-layout { - margin-inline: var(--webchat-spacingHorizontalNone); - position: static; - } - - .stacked-layout__main .stacked-layout__title { - color: var(--webchat-colorNeutralForeground4); - font-size: var(--webchat-fontSizeBase400); - line-height: var(--webchat-lineHeightBase400); - margin: var(--webchat-part-grouping__bubble--space-block) var(--webchat-part-grouping__bubble--space-inline); - } - - &.stacked-layout--no-message .stacked-layout__attachment-row:first-child { - margin-block-start: 0; - } - } - - .stacked-layout .stacked-layout__main .stacked-layout__message-status { - margin-block-start: var(--webchat-part-grouping__message-status--space-block); - } - - .stacked-layout .stacked-layout__content:has(.bubble) { - max-width: 100%; - overflow: visible; - } - - .stacked-layout .stacked-layout__status { - font-size: var(--webchat__font-size--small); - line-height: var(--webchat__line-height--small); - } - /* #endregion */ - - /* #region Carousel layout */ - .carousel-layout { - .carousel-filmstrip__avatar-gutter, - .carousel-filmstrip__alignment-pad { - margin: 0; - width: var(--webchat-spacingHorizontalMNudge); - } - } - /* #endregion */ - - /* #region Bubble surface */ - .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; - } - - .stacked-layout .bubble:has(:global(.activity-loader)) .bubble__content { - background: unset; - box-shadow: unset; - border-color: transparent; - } - - .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); - border-width: 0; - box-shadow: var(--webchat-part-grouping__bubble-content--shadow); - box-sizing: border-box; - color: var(--webchat-colorNeutralForeground1); - max-width: 100%; - min-height: var(--webchat-part-grouping__bubble--min-height); - } - - /* Forced colors remove the bubble shadow and fill, so draw a border as the default theme does. */ - @media (forced-colors: active) { - &:not(.part-grouping-decorator--group) - .stacked-layout - .bubble:not(:has(:global(.activity-loader))) - .bubble__content { - border: var(--webchat-strokeWidthThin) solid CanvasText; - } } .stacked-layout .bubble .text-content { @@ -522,13 +372,7 @@ .collapsible-content__content { margin-block: var(--webchat-part-grouping__collapsible-content--space-block-start-end); - .stacked-layout__attachment-list { - gap: var(--webchat-spacingVerticalS); - } - .stacked-layout__attachment-row { - margin: 0; - .text-content { padding-block: 0; } @@ -548,11 +392,6 @@ var(--webchat-spacingHorizontalNone); --webchat-part-grouping__collapsible-content--space-block-start-end: var(--webchat-spacingVerticalS) var(--webchat-spacingVerticalNone); - - .stacked-layout .stacked-layout__main .stacked-layout__title, - .stacked-layout .stacked-layout__main .collapsible-content__summary { - margin: var(--webchat-spacingVerticalXS) var(--webchat-part-grouping__bubble--space-inline); - } } /* #endregion */ @@ -560,10 +399,6 @@ &.variant-fluent { --webchat-part-grouping__bubble--box-shadow: var(--webchat-shadow4); - .stacked-layout .stacked-layout__attachment-row { - margin-block-start: var(--webchat-spacingVerticalMNudge); - } - &.part-grouping-decorator--group { --webchat-part-grouping__bubble--min-height: 20px; @@ -576,15 +411,6 @@ transition: background-color var(--webchat-durationNormal) var(--webchat-curveDecelerateMid), box-shadow var(--webchat-durationNormal) var(--webchat-curveDecelerateMid); - - .collapsible-grouping__header .stacked-layout__message-status { - margin-block-start: var(--webchat-spacingVerticalNone); - margin-inline-start: var(--webchat-spacingHorizontalNone); - } - - .stacked-layout .stacked-layout__message-status { - margin-inline-end: calc(var(--webchat-spacingHorizontalS) * -1); - } } &:has(.collapsible-grouping--open) { @@ -593,19 +419,6 @@ box-shadow: var(--webchat-part-grouping__bubble--box-shadow); } } - - .bubble { - position: static; - } - - .border-flair { - border-radius: var(--webchat-part-grouping__bubble--border-radius); - } - - .bubble .bubble__content { - margin-block: calc(var(--webchat-spacingVerticalS) * -1); - padding-block: var(--webchat-spacingVerticalS); - } } } /* #endregion */ @@ -633,22 +446,7 @@ margin-inline-end: var(--webchat-spacingHorizontalSNudge); - .stacked-layout { - margin: 0; - position: static; - } - - .carousel-layout { - .carousel-filmstrip__avatar-gutter, - .carousel-filmstrip__alignment-pad { - width: 0; - } - } - .bubble { - position: static; - width: var(--webchat-part-grouping__bubble--max-width); - .bubble__content .adaptive-card-renderer > :global(.ac-adaptiveCard) { font-family: var(--webchat-fontFamilyBase); /* Remove double padding for adaptive cards inside bubble */ @@ -714,28 +512,8 @@ } } - &:not(.part-grouping-decorator--group) { - .bubble__content { - display: flex; - flex-direction: column; - gap: var(--webchat-spacingVerticalS); - } - } - .bubble .collapsible-content .collapsible-content__content { margin-block: var(--webchat-spacingVerticalNone); - - .stacked-layout__attachment-list { - margin-block-start: var(--webchat-spacingVerticalS); - } - - .stacked-layout__attachment { - max-width: var(--webchat-part-grouping__bubble--max-width); - } - } - - .stacked-layout__status { - display: none; } } @@ -763,73 +541,16 @@ } .bubble .bubble__content { - margin-block: calc(var(--webchat-spacingVerticalS) * -1); - padding-block: var(--webchat-spacingVerticalS); - - .stacked-layout__bubble .stacked-layout__title, - .stacked-layout__bubble .collapsible-content__summary { - color: var(--webchat-colorNeutralForeground2); - margin-block: var(--webchat-spacingVerticalXXS); - } - - :global(.stacked-loyout__bubble) .stacked-layout__message-status, - .stacked-layout__bubble .stacked-layout__title, - .stacked-layout__bubble .collapsible-content__summary, - .stacked-layout__bubble .text-content { - font-size: var(--webchat-fontSizeBase200); - line-height: var(--webchat-lineHeightBase200); - } - .stacked-layout__bubble .text-content { color: var(--webchat-colorNeutralForeground3); } } } - &.part-grouping-decorator--from-bot:not(.part-grouping-decorator--group) .bubble__content { - box-sizing: content-box; - padding-block: var(--webchat-spacingVerticalM); - padding-inline: var(--webchat-spacingHorizontalL); - } - - &:not(.part-grouping-decorator--group) .stacked-layout .bubble .bubble__content { - border-width: var(--webchat-strokeWidthThin); - border-style: solid; - } - - &:not(.part-grouping-decorator--group) .bubble:not(.bubble--from-user) .bubble__content { - anchor-name: --webchat-flair; - max-width: unset; - } - - .activity-status { - margin: 0 0 var(--webchat-spacingHorizontalXXS); - } - - .stacked-layout__status { - order: -1; - } - - &:has(.bubble--from-user) { - .bubble { - margin-block-end: var(--webchat-spacingVerticalM); - } - } - - &:has(.bubble.bubble--from-user), - &:has(:global(.pre-chat-message-activity)), - &:has(:global(.liner-message-activity)) { - padding-inline-start: var(--webchat-spacingHorizontalNone); - } - .bubble .text-content .text-content__generated-badge { display: none; } - .stacked-layout { - margin-inline: var(--webchat-spacingHorizontalSNudge); - } - .stacked-layout:has(.collapsible-grouping) { margin-inline: var(--webchat-spacingHorizontalNone); } @@ -894,51 +615,6 @@ .collapsible-grouping__content:not(.collapsible-grouping__content--open) .collapsible-grouping__list { padding: var(--webchat-spacingVerticalNone) var(--webchat-spacingHorizontalNone); - - > *:not(:has(.stacked-layout__message-status--incomplete)) { - height: 0; - margin: 0; - visibility: hidden; - overflow: clip; - } - - > *:has(.stacked-layout__message-status--incomplete) { - /* We use animation so we can reset needed properties immediately upon switching classes */ - animation: collapsible-grouping__fade-in var(--webchat__transition-duration) var(--webchat__transition-duration) - var(--webchat__transition-easing) both; - pointer-events: none; - - .stacked-layout__message-status--incomplete, - .collapsible-content__chevron { - visibility: hidden; - } - } - - &:has(.stacked-layout__message-status--incomplete) { - padding: var(--webchat-spacingVerticalNone) var(--webchat-spacingHorizontalNone); - transform: translate(-4px, -20px); - margin-block-end: calc(-20px - var(--webchat--collapsible-grouping__list--gap)); - } - } - - .stacked-layout .stacked-layout__message-status { - --webchat__component-icon--size: 14px; - - color: var(--webchat-colorNeutralForeground2); - height: 20px; - margin-inline: var(--webchat-spacingHorizontalNone) var(--webchat-spacingHorizontalS); - width: 20px; - - .component-icon { - height: unset; - width: unset; - } - } - - .border-flair { - border-radius: var(--webchat-borderRadius2XLarge); - inset: anchor(top) anchor(right) anchor(bottom) anchor(left); - position-anchor: --webchat-flair; } } /* #endregion */ diff --git a/packages/fluent-theme/src/components/theme/Theme.module.css b/packages/fluent-theme/src/components/theme/Theme.module.css index acb831509c..de02ea26ad 100644 --- a/packages/fluent-theme/src/components/theme/Theme.module.css +++ b/packages/fluent-theme/src/components/theme/Theme.module.css @@ -240,25 +240,6 @@ /* Transcript focused content */ :global(.webchat-fluent).theme .transcript-focus-area:focus-visible .transcript-focus-area__content--focused { - /* Transcript activity focused directly */ - > :global(.webchat__focus-trap) > :global(.webchat__basic-transcript__activity-body) .bubble { - display: grid; - grid-template-areas: 'focused-content'; - position: static; - - .bubble__content { - grid-area: focused-content; - } - - &::after { - border-radius: var(--webchat-part-grouping__bubble--border-radius); - content: ''; - grid-area: focused-content; - outline: var(--webchat-strokeWidthThick) solid var(--webchat-colorStrokeFocus2); - pointer-events: none; - } - } - .collapsible-grouping::after { border-radius: var(--webchat-part-grouping__bubble--border-radius); border: none; @@ -269,10 +250,6 @@ pointer-events: none; position: absolute; } - - :global(.activity-decorator) .bubble .bubble__nub-pad { - display: none; - } } /* Transcript focused content copilot variant */ diff --git a/packages/fluent-theme/src/overrides/ActivityStatus.module.css b/packages/fluent-theme/src/overrides/ActivityStatus.module.css new file mode 100644 index 0000000000..a0c463d4bf --- /dev/null +++ b/packages/fluent-theme/src/overrides/ActivityStatus.module.css @@ -0,0 +1,7 @@ +:global(.webchat-fluent) .part-grouping-decorator { + &.variant-copilot { + .activity-status { + margin: 0 0 var(--webchat-spacingHorizontalXXS); + } + } +} diff --git a/packages/fluent-theme/src/overrides/BorderFlair.module.css b/packages/fluent-theme/src/overrides/BorderFlair.module.css new file mode 100644 index 0000000000..4e9eb18ad6 --- /dev/null +++ b/packages/fluent-theme/src/overrides/BorderFlair.module.css @@ -0,0 +1,16 @@ +:global(.webchat-fluent) .part-grouping-decorator { + &.variant-fluent { + &.part-grouping-decorator--group { + .border-flair { + border-radius: var(--webchat-part-grouping__bubble--border-radius); + } + } + } + &.variant-copilot { + .border-flair { + border-radius: var(--webchat-borderRadius2XLarge); + inset: anchor(top) anchor(right) anchor(bottom) anchor(left); + position-anchor: --webchat-flair; + } + } +} diff --git a/packages/fluent-theme/src/overrides/Bubble.module.css b/packages/fluent-theme/src/overrides/Bubble.module.css new file mode 100644 index 0000000000..7448388bfc --- /dev/null +++ b/packages/fluent-theme/src/overrides/Bubble.module.css @@ -0,0 +1,153 @@ +:global(.webchat-fluent) .part-grouping-decorator { + .stacked-layout .bubble :global(.webchat__fileContent__badge) { + cursor: default; + font-size: var(--webchat-fontSizeBase300); + line-height: var(--webchat-lineHeightBase300); + + :global(.webchat__fileContent__fileName) { + color: var(--webchat-colorBrandForegroundLink); + font-family: inherit; + } + + :global(.webchat__fileContent__size) { + color: var(--webchat-colorNeutralForeground2); + font-family: inherit; + } + } + + .stacked-layout .bubble :global(.webchat__fileContent__downloadIcon) { + color: var(--webchat-colorBrandForegroundLink); + } + + .collapsible-grouping { + :global(.part-grouping-decorator) .bubble .bubble__content { + box-sizing: content-box; + gap: var(--webchat-spacingVerticalXS); + } + } + + .stacked-layout .stacked-layout__content:has(.bubble) { + max-width: 100%; + overflow: visible; + } + .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; + } + + .stacked-layout .bubble:has(:global(.activity-loader)) .bubble__content { + background: unset; + box-shadow: unset; + border-color: transparent; + } + + .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); + border-width: 0; + box-shadow: var(--webchat-part-grouping__bubble-content--shadow); + box-sizing: border-box; + color: var(--webchat-colorNeutralForeground1); + max-width: 100%; + min-height: var(--webchat-part-grouping__bubble--min-height); + } + + /* Forced colors remove the bubble shadow and fill, so draw a border as the default theme does. */ + @media (forced-colors: active) { + &:not(.part-grouping-decorator--group) + .stacked-layout + .bubble:not(:has(:global(.activity-loader))) + .bubble__content { + border: var(--webchat-strokeWidthThin) solid CanvasText; + } + } + &.variant-fluent { + &.part-grouping-decorator--group { + .bubble { + position: static; + } + + .bubble .bubble__content { + margin-block: calc(var(--webchat-spacingVerticalS) * -1); + padding-block: var(--webchat-spacingVerticalS); + } + } + } + &.variant-copilot { + &.part-grouping-decorator--from-bot { + .bubble { + position: static; + width: var(--webchat-part-grouping__bubble--max-width); + } + + &:not(.part-grouping-decorator--group) { + .bubble__content { + display: flex; + flex-direction: column; + gap: var(--webchat-spacingVerticalS); + } + } + } + + &.part-grouping-decorator--group { + .bubble .bubble__content { + margin-block: calc(var(--webchat-spacingVerticalS) * -1); + padding-block: var(--webchat-spacingVerticalS); + } + } + + &.part-grouping-decorator--from-bot:not(.part-grouping-decorator--group) .bubble__content { + box-sizing: content-box; + padding-block: var(--webchat-spacingVerticalM); + padding-inline: var(--webchat-spacingHorizontalL); + } + + &:not(.part-grouping-decorator--group) .stacked-layout .bubble .bubble__content { + border-width: var(--webchat-strokeWidthThin); + border-style: solid; + } + + &:not(.part-grouping-decorator--group) .bubble:not(.bubble--from-user) .bubble__content { + anchor-name: --webchat-flair; + max-width: unset; + } + + &:has(.bubble--from-user) { + .bubble { + margin-block-end: var(--webchat-spacingVerticalM); + } + } + + &:has(.bubble.bubble--from-user), + &:has(:global(.pre-chat-message-activity)), + &:has(:global(.liner-message-activity)) { + padding-inline-start: var(--webchat-spacingHorizontalNone); + } + } +} +:global(.webchat-fluent).theme .transcript-focus-area:focus-visible .transcript-focus-area__content--focused { + /* Transcript activity focused directly */ + > :global(.webchat__focus-trap) > :global(.webchat__basic-transcript__activity-body) .bubble { + display: grid; + grid-template-areas: 'focused-content'; + position: static; + + .bubble__content { + grid-area: focused-content; + } + + &::after { + border-radius: var(--webchat-part-grouping__bubble--border-radius); + content: ''; + grid-area: focused-content; + outline: var(--webchat-strokeWidthThick) solid var(--webchat-colorStrokeFocus2); + pointer-events: none; + } + } + + :global(.activity-decorator) .bubble .bubble__nub-pad { + display: none; + } +} diff --git a/packages/fluent-theme/src/overrides/CarouselFilmStrip.module.css b/packages/fluent-theme/src/overrides/CarouselFilmStrip.module.css new file mode 100644 index 0000000000..67dc41febb --- /dev/null +++ b/packages/fluent-theme/src/overrides/CarouselFilmStrip.module.css @@ -0,0 +1,19 @@ +:global(.webchat-fluent) .part-grouping-decorator { + .carousel-layout { + .carousel-filmstrip__avatar-gutter, + .carousel-filmstrip__alignment-pad { + margin: 0; + width: var(--webchat-spacingHorizontalMNudge); + } + } + &.variant-copilot { + &.part-grouping-decorator--from-bot { + .carousel-layout { + .carousel-filmstrip__avatar-gutter, + .carousel-filmstrip__alignment-pad { + width: 0; + } + } + } + } +} diff --git a/packages/fluent-theme/src/overrides/ComponentIcon.module.css b/packages/fluent-theme/src/overrides/ComponentIcon.module.css new file mode 100644 index 0000000000..902fb7a11e --- /dev/null +++ b/packages/fluent-theme/src/overrides/ComponentIcon.module.css @@ -0,0 +1,18 @@ +:global(.webchat-fluent) .part-grouping-decorator { + .collapsible-grouping { + .stacked-layout__message-status { + .component-icon { + height: 19px; + width: 19px; + } + } + } + &.variant-copilot { + .stacked-layout .stacked-layout__message-status { + .component-icon { + height: unset; + width: unset; + } + } + } +} diff --git a/packages/fluent-theme/src/overrides/MessageStatusLoader.module.css b/packages/fluent-theme/src/overrides/MessageStatusLoader.module.css new file mode 100644 index 0000000000..74d42566f2 --- /dev/null +++ b/packages/fluent-theme/src/overrides/MessageStatusLoader.module.css @@ -0,0 +1,13 @@ +:global(.webchat-fluent) .part-grouping-decorator { + .message-status-loader { + --webchat__component-icon--mask: unset; + --webchat__component-icon--image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22100%22%20height%3D%22100%22%20viewBox%3D%220%200%20100%20100%22%3E%3Cdefs%3E%3ClinearGradient%20id%3D%22a%22%20x1%3D%22-.5%22%20x2%3D%221.5%22%20y1%3D%22.5%22%20y2%3D%22.5%22%20gradientUnits%3D%22objectBoundingBox%22%3E%3Cstop%20offset%3D%220%22%20stop-color%3D%22%23d161aa%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%23e96d80%22%2F%3E%3C%2FlinearGradient%3E%3ClinearGradient%20id%3D%22b%22%20x1%3D%22-.5%22%20x2%3D%221.5%22%20y1%3D%22.5%22%20y2%3D%22.5%22%20gradientUnits%3D%22objectBoundingBox%22%3E%3Cstop%20offset%3D%220%22%20stop-color%3D%22%2342a3a6%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%2373af69%22%2F%3E%3C%2FlinearGradient%3E%3ClinearGradient%20id%3D%22c%22%20x1%3D%22-.5%22%20x2%3D%221.5%22%20y1%3D%22.5%22%20y2%3D%22.5%22%20gradientUnits%3D%22objectBoundingBox%22%3E%3Cstop%20offset%3D%220%22%20stop-color%3D%22%2365a5dd%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%238173ee%22%2F%3E%3C%2FlinearGradient%3E%3C%2Fdefs%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22url(%23a)%22%3E%3Canimate%20attributeName%3D%22r%22%20begin%3D%220s%22%20calcMode%3D%22spline%22%20dur%3D%223.6s%22%20keySplines%3D%220.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%22%20keyTimes%3D%220%3B0.1667%3B0.3333%3B0.5%3B0.6667%3B0.8333%3B1%22%20repeatCount%3D%22indefinite%22%20values%3D%2250%3B0%3B0%3B0%3B0%3B49%3B49%22%2F%3E%3Canimate%20attributeName%3D%22opacity%22%20begin%3D%220s%22%20calcMode%3D%22spline%22%20dur%3D%223.6s%22%20keySplines%3D%220.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%22%20keyTimes%3D%220%3B0.1667%3B0.3333%3B0.5%3B0.6667%3B0.6668%3B1%22%20repeatCount%3D%22indefinite%22%20values%3D%221%3B0%3B0%3B0%3B0%3B1%3B1%22%2F%3E%3Canimate%20attributeName%3D%22cx%22%20begin%3D%220s%22%20calcMode%3D%22spline%22%20dur%3D%223.6s%22%20keySplines%3D%220.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%22%20keyTimes%3D%220%3B0.1667%3B0.3333%3B0.5%3B0.6667%3B0.8333%3B1%22%20repeatCount%3D%22indefinite%22%20values%3D%2250%3B100%3B0%3B0%3B0%3B50%3B50%22%2F%3E%3C%2Fcircle%3E%3Ccircle%20cy%3D%2250%22%20r%3D%220%22%20fill%3D%22url(%23b)%22%20opacity%3D%220%22%3E%3Canimate%20attributeName%3D%22r%22%20begin%3D%220s%22%20calcMode%3D%22spline%22%20dur%3D%223.6s%22%20keySplines%3D%220.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%22%20keyTimes%3D%220%3B0.1667%3B0.3333%3B0.5%3B0.6667%3B0.8333%3B1%22%20repeatCount%3D%22indefinite%22%20values%3D%220%3B49%3B49%3B0%3B0%3B0%3B0%22%2F%3E%3Canimate%20attributeName%3D%22opacity%22%20begin%3D%220s%22%20calcMode%3D%22spline%22%20dur%3D%223.6s%22%20keySplines%3D%220.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%22%20keyTimes%3D%220%3B0.0001%3B0.3333%3B0.5%3B0.6667%3B0.8333%3B1%22%20repeatCount%3D%22indefinite%22%20values%3D%220%3B1%3B1%3B0%3B0%3B0%3B0%22%2F%3E%3Canimate%20attributeName%3D%22cx%22%20begin%3D%220s%22%20calcMode%3D%22spline%22%20dur%3D%223.6s%22%20keySplines%3D%220.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%22%20keyTimes%3D%220%3B0.1667%3B0.3333%3B0.5%3B0.6667%3B0.8333%3B1%22%20repeatCount%3D%22indefinite%22%20values%3D%220%3B50%3B50%3B100%3B0%3B0%3B0%22%2F%3E%3C%2Fcircle%3E%3Ccircle%20cy%3D%2250%22%20r%3D%220%22%20fill%3D%22url(%23c)%22%20opacity%3D%220%22%3E%3Canimate%20attributeName%3D%22r%22%20begin%3D%220s%22%20calcMode%3D%22spline%22%20dur%3D%223.6s%22%20keySplines%3D%220.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%22%20keyTimes%3D%220%3B0.1667%3B0.3333%3B0.5%3B0.6667%3B0.8333%3B1%22%20repeatCount%3D%22indefinite%22%20values%3D%220%3B0%3B0%3B49%3B49%3B0%3B0%22%2F%3E%3Canimate%20attributeName%3D%22opacity%22%20begin%3D%220s%22%20calcMode%3D%22spline%22%20dur%3D%223.6s%22%20keySplines%3D%220.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%22%20keyTimes%3D%220%3B0.1667%3B0.1668%3B0.5%3B0.6667%3B0.8333%3B1%22%20repeatCount%3D%22indefinite%22%20values%3D%220%3B0%3B0%3B1%3B1%3B0%3B0%22%2F%3E%3Canimate%20attributeName%3D%22cx%22%20begin%3D%220s%22%20calcMode%3D%22spline%22%20dur%3D%223.6s%22%20keySplines%3D%220.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%22%20keyTimes%3D%220%3B0.1667%3B0.3333%3B0.5%3B0.6667%3B0.8333%3B1%22%20repeatCount%3D%22indefinite%22%20values%3D%220%3B0%3B0%3B50%3B50%3B100%3B0%22%2F%3E%3C%2Fcircle%3E%3C%2Fsvg%3E'); + + animation: none; + color: transparent; + + @media (prefers-reduced-motion: reduce) { + --webchat__component-icon--image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22100%22%20height%3D%22100%22%20viewBox%3D%220%200%20100%20100%22%3E%3Cdefs%3E%3ClinearGradient%20id%3D%22a%22%20x1%3D%22-.5%22%20x2%3D%221.5%22%20y1%3D%22.5%22%20y2%3D%22.5%22%20gradientUnits%3D%22objectBoundingBox%22%3E%3Cstop%20offset%3D%220%22%20stop-color%3D%22%23d161aa%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%23e96d80%22%2F%3E%3C%2FlinearGradient%3E%3C%2Fdefs%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2249%22%20fill%3D%22url(%23a)%22%2F%3E%3C%2Fsvg%3E'); + } + } +} diff --git a/packages/fluent-theme/src/overrides/StackedLayout.module.css b/packages/fluent-theme/src/overrides/StackedLayout.module.css new file mode 100644 index 0000000000..e4819b6082 --- /dev/null +++ b/packages/fluent-theme/src/overrides/StackedLayout.module.css @@ -0,0 +1,176 @@ +:global(.webchat-fluent) .part-grouping-decorator { + .collapsible-grouping__content .stacked-layout .stacked-layout__title, + .collapsible-grouping__title { + color: var(--webchat-colorNeutralForeground2); + font-size: var(--webchat-fontSizeBase300); + font-weight: var(--webchat-fontWeightSemibold); + line-height: var(--webchat-lineHeightBase300); + } + + .stacked-layout .stacked-layout__message-status { + --webchat__component-icon--size: 14px; + + color: var(--webchat-colorNeutralForeground2); + font-size: var(--webchat-fontSizeBase400); + } + + .collapsible-grouping { + .stacked-layout__message-status { + height: unset; + width: unset; + } + + .stacked-layout :global(.activity-loader) { + display: none; + } + } + .stacked-layout { + display: flex; + flex-flow: column nowrap; + margin-inline: var(--webchat-spacingHorizontalMNudge); + + .stacked-layout { + margin-inline: var(--webchat-spacingHorizontalNone); + position: static; + } + + .stacked-layout__main .stacked-layout__title { + color: var(--webchat-colorNeutralForeground4); + font-size: var(--webchat-fontSizeBase400); + line-height: var(--webchat-lineHeightBase400); + margin: var(--webchat-part-grouping__bubble--space-block) var(--webchat-part-grouping__bubble--space-inline); + } + + &.stacked-layout--no-message .stacked-layout__attachment-row:first-child { + margin-block-start: 0; + } + } + + .stacked-layout .stacked-layout__main .stacked-layout__message-status { + margin-block-start: var(--webchat-part-grouping__message-status--space-block); + } + + .stacked-layout .stacked-layout__status { + font-size: var(--webchat__font-size--small); + line-height: var(--webchat__line-height--small); + } + .stacked-layout .bubble .collapsible-content { + .collapsible-content__content { + .stacked-layout__attachment-list { + gap: var(--webchat-spacingVerticalS); + } + + .stacked-layout__attachment-row { + margin: 0; + } + } + } + &.part-grouping-decorator--group { + .stacked-layout .stacked-layout__main .stacked-layout__title, + .stacked-layout .stacked-layout__main .collapsible-content__summary { + margin: var(--webchat-spacingVerticalXS) var(--webchat-part-grouping__bubble--space-inline); + } + } + &.variant-fluent { + .stacked-layout .stacked-layout__attachment-row { + margin-block-start: var(--webchat-spacingVerticalMNudge); + } + + &.part-grouping-decorator--group { + .collapsible-grouping { + .collapsible-grouping__header .stacked-layout__message-status { + margin-block-start: var(--webchat-spacingVerticalNone); + margin-inline-start: var(--webchat-spacingHorizontalNone); + } + + .stacked-layout .stacked-layout__message-status { + margin-inline-end: calc(var(--webchat-spacingHorizontalS) * -1); + } + } + } + } + &.variant-copilot { + &.part-grouping-decorator--from-bot { + .stacked-layout { + margin: 0; + position: static; + } + + .bubble .collapsible-content .collapsible-content__content { + .stacked-layout__attachment-list { + margin-block-start: var(--webchat-spacingVerticalS); + } + + .stacked-layout__attachment { + max-width: var(--webchat-part-grouping__bubble--max-width); + } + } + + .stacked-layout__status { + display: none; + } + } + + &.part-grouping-decorator--group { + .bubble .bubble__content { + .stacked-layout__bubble .stacked-layout__title, + .stacked-layout__bubble .collapsible-content__summary { + color: var(--webchat-colorNeutralForeground2); + margin-block: var(--webchat-spacingVerticalXXS); + } + + :global(.stacked-loyout__bubble) .stacked-layout__message-status, + .stacked-layout__bubble .stacked-layout__title, + .stacked-layout__bubble .collapsible-content__summary, + .stacked-layout__bubble .text-content { + font-size: var(--webchat-fontSizeBase200); + line-height: var(--webchat-lineHeightBase200); + } + } + } + + .stacked-layout__status { + order: -1; + } + + .stacked-layout { + margin-inline: var(--webchat-spacingHorizontalSNudge); + } + + .collapsible-grouping__content:not(.collapsible-grouping__content--open) .collapsible-grouping__list { + > *:not(:has(.stacked-layout__message-status--incomplete)) { + height: 0; + margin: 0; + visibility: hidden; + overflow: clip; + } + + > *:has(.stacked-layout__message-status--incomplete) { + /* We use animation so we can reset needed properties immediately upon switching classes */ + animation: collapsible-grouping__fade-in var(--webchat__transition-duration) var(--webchat__transition-duration) + var(--webchat__transition-easing) both; + pointer-events: none; + + .stacked-layout__message-status--incomplete, + .collapsible-content__chevron { + visibility: hidden; + } + } + + &:has(.stacked-layout__message-status--incomplete) { + padding: var(--webchat-spacingVerticalNone) var(--webchat-spacingHorizontalNone); + transform: translate(-4px, -20px); + margin-block-end: calc(-20px - var(--webchat--collapsible-grouping__list--gap)); + } + } + + .stacked-layout .stacked-layout__message-status { + --webchat__component-icon--size: 14px; + + color: var(--webchat-colorNeutralForeground2); + height: 20px; + margin-inline: var(--webchat-spacingHorizontalNone) var(--webchat-spacingHorizontalS); + width: 20px; + } + } +} diff --git a/packages/fluent-theme/src/private/fluentCSSModuleOverrides.ts b/packages/fluent-theme/src/private/fluentCSSModuleOverrides.ts index 198547fabe..0e7efcb2a9 100644 --- a/packages/fluent-theme/src/private/fluentCSSModuleOverrides.ts +++ b/packages/fluent-theme/src/private/fluentCSSModuleOverrides.ts @@ -7,16 +7,10 @@ const partGroupingDecoratorOverrides: readonly string[] = Object.freeze([ 'activity-button', 'activity-button__text', - 'activity-status', 'adaptive-card-renderer', 'adaptive-card-renderer__action--selected', - 'border-flair', 'bubble', - 'bubble--from-user', 'bubble__content', - 'carousel-filmstrip__alignment-pad', - 'carousel-filmstrip__avatar-gutter', - 'carousel-layout', 'collapsible-content', 'collapsible-content__chevron', 'collapsible-content__content', @@ -29,7 +23,6 @@ const partGroupingDecoratorOverrides: readonly string[] = Object.freeze([ 'collapsible-grouping__header', 'collapsible-grouping__list', 'collapsible-grouping__title', - 'component-icon', 'link-definitions', 'link-definitions__badge', 'link-definitions__header', @@ -44,21 +37,11 @@ const partGroupingDecoratorOverrides: readonly string[] = Object.freeze([ 'link-definitions__list-item-text', 'link-definitions__message-sensitivity-label-text', 'link-definitions__open-in-new-window-icon', - 'message-status-loader', 'part-grouping-activity__activities', 'render-markdown', 'stacked-layout', - 'stacked-layout--no-message', - 'stacked-layout__attachment', - 'stacked-layout__attachment-list', 'stacked-layout__attachment-row', 'stacked-layout__bubble', - 'stacked-layout__content', - 'stacked-layout__main', - 'stacked-layout__message-status', - 'stacked-layout__message-status--incomplete', - 'stacked-layout__status', - 'stacked-layout__title', 'text-content', 'text-content__generated-badge' ]); @@ -71,9 +54,6 @@ const themeOverrides: readonly string[] = Object.freeze([ 'activity-button', 'activity-button__icon', 'animated-image-content__button', - 'bubble', - 'bubble__content', - 'bubble__nub-pad', 'collapsible-grouping', 'transcript-focus-area', 'transcript-focus-area__content--focused' diff --git a/packages/fluent-theme/src/private/fluentCSSModuleTheme.ts b/packages/fluent-theme/src/private/fluentCSSModuleTheme.ts index fc7eae700b..9d1ea54b0e 100644 --- a/packages/fluent-theme/src/private/fluentCSSModuleTheme.ts +++ b/packages/fluent-theme/src/private/fluentCSSModuleTheme.ts @@ -2,9 +2,16 @@ import { combineCSSModules } from 'botframework-webchat/internal.js'; import partGroupingDecoratorStyles from '../components/activity/PartGroupingDecorator.module.css'; import themeStyles from '../components/theme/Theme.module.css'; +import messageStatusLoaderOverrideStyles from '../overrides/MessageStatusLoader.module.css'; +import bubbleOverrideStyles from '../overrides/Bubble.module.css'; +import stackedLayoutOverrideStyles from '../overrides/StackedLayout.module.css'; +import componentIconOverrideStyles from '../overrides/ComponentIcon.module.css'; +import carouselFilmStripOverrideStyles from '../overrides/CarouselFilmStrip.module.css'; import codeBlockContentOverrideStyles from '../overrides/CodeBlockContent.module.css'; import codeBlockCopyButtonOverrideStyles from '../overrides/CodeBlockCopyButton.module.css'; import transcriptFocusOverrideStyles from '../overrides/TranscriptFocus.module.css'; +import borderFlairOverrideStyles from '../overrides/BorderFlair.module.css'; +import activityStatusOverrideStyles from '../overrides/ActivityStatus.module.css'; import attachmentBarOverrideStyles from '../overrides/AttachmentBar.module.css'; import attachmentBarItemOverrideStyles from '../overrides/AttachmentBarItem.module.css'; import filePreviewOverrideStyles from '../overrides/FilePreview.module.css'; @@ -22,9 +29,16 @@ import pickCSSModule from './pickCSSModule'; const fluentCSSModuleTheme = combineCSSModules( pickCSSModule(partGroupingDecoratorStyles, partGroupingDecoratorOverrides), pickCSSModule(themeStyles, themeOverrides), + messageStatusLoaderOverrideStyles, + bubbleOverrideStyles, + stackedLayoutOverrideStyles, + componentIconOverrideStyles, + carouselFilmStripOverrideStyles, codeBlockContentOverrideStyles, codeBlockCopyButtonOverrideStyles, transcriptFocusOverrideStyles, + borderFlairOverrideStyles, + activityStatusOverrideStyles, attachmentBarOverrideStyles, attachmentBarItemOverrideStyles, filePreviewOverrideStyles, From 7736c88fd08ae4353afc1b9649d595c996df2732 Mon Sep 17 00:00:00 2001 From: Eugene Olonov Date: Fri, 30 Oct 2026 17:49:54 -0700 Subject: [PATCH 16/24] Move Fluent's rules for part grouping to override modules Rules move verbatim from PartGroupingDecorator.module.css and Theme.module.css to overrides/ActivityButton.module.css, CollapsibleContent.module.css, CollapsibleGrouping.module.css and PartGroupingActivity.module.css. --- .../activity/PartGroupingDecorator.module.css | 169 ------------------ .../src/components/theme/Theme.module.css | 72 -------- .../src/overrides/ActivityButton.module.css | 75 ++++++++ .../overrides/CollapsibleContent.module.css | 22 +++ .../overrides/CollapsibleGrouping.module.css | 156 ++++++++++++++++ .../overrides/PartGroupingActivity.module.css | 6 + .../src/private/fluentCSSModuleOverrides.ts | 22 +-- .../src/private/fluentCSSModuleTheme.ts | 8 + 8 files changed, 268 insertions(+), 262 deletions(-) create mode 100644 packages/fluent-theme/src/overrides/ActivityButton.module.css create mode 100644 packages/fluent-theme/src/overrides/CollapsibleContent.module.css create mode 100644 packages/fluent-theme/src/overrides/CollapsibleGrouping.module.css create mode 100644 packages/fluent-theme/src/overrides/PartGroupingActivity.module.css diff --git a/packages/fluent-theme/src/components/activity/PartGroupingDecorator.module.css b/packages/fluent-theme/src/components/activity/PartGroupingDecorator.module.css index aa11244c2f..6e7c1fa7a3 100644 --- a/packages/fluent-theme/src/components/activity/PartGroupingDecorator.module.css +++ b/packages/fluent-theme/src/components/activity/PartGroupingDecorator.module.css @@ -286,59 +286,6 @@ /* #endregion */ /* #region Header & collapsible grouping */ - .stacked-layout .collapsible-grouping__header { - justify-content: flex-start; - min-width: 0; - padding: var(--webchat-spacingVerticalNone) var(--webchat-spacingHorizontalNone); - place-self: start; - position: relative; - - .activity-button { - background: transparent; - border: none; - cursor: pointer; - font-size: var(--webchat-fontSizeBase300); - padding: var(--webchat-spacingVerticalXXS) var(--webchat-spacingHorizontalXS); - - &::after { - content: ''; - cursor: pointer; - display: block; - inset: 0; - position: absolute; - } - } - - .activity-button__text { - font-size: 0; - height: 0; - overflow: clip; - position: absolute; - width: 0; - } - } - - .stacked-layout .collapsible-grouping__content .part-grouping-activity__activities { - --webchat--collapsible-grouping__list--gap: var(--webchat-spacingVerticalS); - padding: var(--webchat-spacingVerticalL) var(--webchat-spacingHorizontalL); - } - - .stacked-layout .collapsible-grouping { - display: grid; - - &.collapsible-grouping--open { - gap: var(--webchat-spacingVerticalM); - } - } - - .collapsible-grouping, - .collapsible-grouping__header { - border: none; - } - - .collapsible-grouping { - max-width: 100%; - } .stacked-layout .bubble .text-content { font-size: var(--webchat__font-size--medium); @@ -361,17 +308,7 @@ /* #region Collapsible content */ .stacked-layout .bubble .collapsible-content { - .collapsible-content__summary { - &:focus-visible { - border-radius: var(--webchat-borderRadiusSmall); - outline-offset: 4px; - outline: var(--webchat-strokeWidthThick) solid var(--webchat-colorStrokeFocus2); - } - } - .collapsible-content__content { - margin-block: var(--webchat-part-grouping__collapsible-content--space-block-start-end); - .stacked-layout__attachment-row { .text-content { padding-block: 0; @@ -401,24 +338,6 @@ &.part-grouping-decorator--group { --webchat-part-grouping__bubble--min-height: 20px; - - .collapsible-grouping { - anchor-name: --webchat-flair; - border-radius: var(--webchat-part-grouping__bubble--border-radius); - max-width: min(var(--webchat-part-grouping__bubble--max-width), 100%); - padding: var(--webchat-part-grouping__bubble--space-block) var(--webchat-part-grouping__bubble--space-inline); - position: relative; - transition: - background-color var(--webchat-durationNormal) var(--webchat-curveDecelerateMid), - box-shadow var(--webchat-durationNormal) var(--webchat-curveDecelerateMid); - } - - &:has(.collapsible-grouping--open) { - .collapsible-grouping { - background-color: var(--webchat-part-grouping__bubble--background-color); - box-shadow: var(--webchat-part-grouping__bubble--box-shadow); - } - } } } /* #endregion */ @@ -511,10 +430,6 @@ } } } - - .bubble .collapsible-content .collapsible-content__content { - margin-block: var(--webchat-spacingVerticalNone); - } } &.part-grouping-decorator--group { @@ -522,24 +437,6 @@ --webchat-part-grouping__text-content--space-block-inline: var(--webchat-spacingVerticalNone) var(--webchat-spacingHorizontalNone); - .collapsible-grouping__content { - border: var(--webchat-strokeWidthThin) solid transparent; - border-radius: var(--webchat-part-grouping__bubble--border-radius); - position: relative; - - @supports (anchor-name: --webchat-flair) { - anchor-name: --webchat-flair; - position: static; - } - } - - &:has(.collapsible-grouping--open) { - .collapsible-grouping__content { - background-color: var(--webchat-part-grouping__bubble--background-color); - border-color: var(--webchat-part-grouping__bubble--border-color); - } - } - .bubble .bubble__content { .stacked-layout__bubble .text-content { color: var(--webchat-colorNeutralForeground3); @@ -550,72 +447,6 @@ .bubble .text-content .text-content__generated-badge { display: none; } - - .stacked-layout:has(.collapsible-grouping) { - margin-inline: var(--webchat-spacingHorizontalNone); - } - - .collapsible-grouping { - margin-inline: var(--webchat-spacingHorizontalXS); - } - - .collapsible-grouping .collapsible-grouping__chevron, - .collapsible-content .collapsible-content__chevron { - transform: rotate(270deg); - } - - .collapsible-grouping--open .collapsible-grouping__chevron, - .collapsible-content[open] .collapsible-content__chevron { - transform: rotate(360deg); - } - - .collapsible-grouping__header { - margin-inline: calc(var(--webchat-spacingHorizontalXS) * -1); - } - - .collapsible-grouping__title { - color: var(--webchat-colorNeutralForeground2); - font-size: var(--webchat-fontSizeBase200); - font-weight: var(--webchat-fontWeightSemibold); - line-height: var(--webchat-lineHeightBase200); - } - - .collapsible-grouping__content { - anchor-name: --webchat-flair; - background: var(--webchat-colorNeutralBackground1); - background-clip: padding-box; - border-radius: var(--webchat-borderRadius2XLarge); - height: unset; - opacity: unset; - visibility: unset; - } - - .collapsible-grouping__list > * { - interpolate-size: allow-keywords; - transition: - margin calc(var(--webchat__transition-duration) / 2) var(--webchat__transition-easing), - height calc(var(--webchat__transition-duration) / 2) var(--webchat__transition-easing); - } - - .collapsible-grouping__content--open .collapsible-grouping__list > * { - /* We use animation so we can reset needed properties immediately upon switching classes */ - animation: collapsible-grouping__fade-in var(--webchat__transition-duration) - calc(var(--webchat__transition-duration) / 4) var(--webchat__transition-easing) both; - height: auto; - transition: - margin var(--webchat__transition-duration) var(--webchat__transition-easing), - height var(--webchat__transition-duration) var(--webchat__transition-easing); - } - - .collapsible-grouping__content:not(.collapsible-grouping__content--open) { - /* We use animation so we can reset needed properties immediately upon switching classes */ - animation: collapsible-grouping__fade-in var(--webchat__transition-duration) var(--webchat__transition-duration) - var(--webchat__transition-easing) both; - } - - .collapsible-grouping__content:not(.collapsible-grouping__content--open) .collapsible-grouping__list { - padding: var(--webchat-spacingVerticalNone) var(--webchat-spacingHorizontalNone); - } } /* #endregion */ } diff --git a/packages/fluent-theme/src/components/theme/Theme.module.css b/packages/fluent-theme/src/components/theme/Theme.module.css index de02ea26ad..0f9958a315 100644 --- a/packages/fluent-theme/src/components/theme/Theme.module.css +++ b/packages/fluent-theme/src/components/theme/Theme.module.css @@ -238,29 +238,6 @@ scrollbar-gutter: stable; } -/* Transcript focused content */ -:global(.webchat-fluent).theme .transcript-focus-area:focus-visible .transcript-focus-area__content--focused { - .collapsible-grouping::after { - border-radius: var(--webchat-part-grouping__bubble--border-radius); - border: none; - content: ''; - inset: 0; - isolation: isolate; - outline: var(--webchat-strokeWidthThick) solid var(--webchat-colorStrokeFocus2); - pointer-events: none; - position: absolute; - } -} - -/* Transcript focused content copilot variant */ -:global(.webchat-fluent).theme.variant-copilot - .transcript-focus-area:focus-visible - .transcript-focus-area__content--focused { - .collapsible-grouping::after { - inset: -4px; - } -} - /* Transcript filer in copilot variant */ :global(.webchat-fluent).theme.variant-copilot :global(.webchat__basic-transcript .webchat__basic-transcript__filler) { /* No filler unless pre-chat activity, see next rule */ @@ -322,55 +299,6 @@ } } -/* Code block */ - -/* View Code dialog */ - -/* Activity button */ -:global(.webchat-fluent).theme .activity-button { - background: var(--webchat-colorNeutralBackground1); - border-radius: var(--webchat-borderRadiusMedium); - border: var(--webchat-strokeWidthThin) solid var(--webchat-colorNeutralStroke1); - color: var(--webchat-colorNeutralForeground1); - gap: var(--webchat-spacingHorizontalXS); - padding: 5px var(--webchat-spacingHorizontalM); - - &:hover { - background: var(--webchat-colorNeutralBackground1Hover); - border: var(--webchat-strokeWidthThin) solid var(--webchat-colorNeutralStroke1Hover); - color: var(--webchat-colorNeutralForeground1Hover); - } - - &:active { - background: var(--webchat-colorNeutralBackground1Pressed); - border: var(--webchat-strokeWidthThin) solid var(--webchat-colorNeutralStroke1Pressed); - color: var(--webchat-colorNeutralForeground1Pressed); - } - - &:focus-visible { - background: var(--webchat-colorNeutralBackground1); - outline: var(--webchat-strokeWidthThick) solid var(--webchat-colorStrokeFocus2); - outline-offset: calc(var(--webchat-strokeWidthThick) * -1); - } - - &[aria-disabled='true'] { - background: var(--webchat-colorNeutralBackgroundDisabled); - border: var(--webchat-strokeWidthThin) solid var(--webchat-colorNeutralStrokeDisabled); - color: var(--webchat-colorNeutralForegroundDisabled); - } - - .activity-button__icon { - height: 20px; - width: 20px; - - --webchat__component-icon--color: var(--webchat-colorNeutralForeground2); - - @media (forced-colors: active) { - --webchat__component-icon--color: buttonText; - } - } -} - /* Pause button over animated images: a band in the button background around the focus indicator keeps it visible over any image */ :global(.webchat-fluent).theme .activity-button.animated-image-content__button:focus-visible::after { outline: var(--webchat-strokeWidthThick) solid var(--webchat-colorNeutralBackground1); diff --git a/packages/fluent-theme/src/overrides/ActivityButton.module.css b/packages/fluent-theme/src/overrides/ActivityButton.module.css new file mode 100644 index 0000000000..8845a5a0ba --- /dev/null +++ b/packages/fluent-theme/src/overrides/ActivityButton.module.css @@ -0,0 +1,75 @@ +:global(.webchat-fluent) .part-grouping-decorator { + .stacked-layout .collapsible-grouping__header { + .activity-button { + background: transparent; + border: none; + cursor: pointer; + font-size: var(--webchat-fontSizeBase300); + padding: var(--webchat-spacingVerticalXXS) var(--webchat-spacingHorizontalXS); + + &::after { + content: ''; + cursor: pointer; + display: block; + inset: 0; + position: absolute; + } + } + + .activity-button__text { + font-size: 0; + height: 0; + overflow: clip; + position: absolute; + width: 0; + } + } +} +/* Code block */ + +/* View Code dialog */ + +/* Activity button */ +:global(.webchat-fluent).theme .activity-button { + background: var(--webchat-colorNeutralBackground1); + border-radius: var(--webchat-borderRadiusMedium); + border: var(--webchat-strokeWidthThin) solid var(--webchat-colorNeutralStroke1); + color: var(--webchat-colorNeutralForeground1); + gap: var(--webchat-spacingHorizontalXS); + padding: 5px var(--webchat-spacingHorizontalM); + + &:hover { + background: var(--webchat-colorNeutralBackground1Hover); + border: var(--webchat-strokeWidthThin) solid var(--webchat-colorNeutralStroke1Hover); + color: var(--webchat-colorNeutralForeground1Hover); + } + + &:active { + background: var(--webchat-colorNeutralBackground1Pressed); + border: var(--webchat-strokeWidthThin) solid var(--webchat-colorNeutralStroke1Pressed); + color: var(--webchat-colorNeutralForeground1Pressed); + } + + &:focus-visible { + background: var(--webchat-colorNeutralBackground1); + outline: var(--webchat-strokeWidthThick) solid var(--webchat-colorStrokeFocus2); + outline-offset: calc(var(--webchat-strokeWidthThick) * -1); + } + + &[aria-disabled='true'] { + background: var(--webchat-colorNeutralBackgroundDisabled); + border: var(--webchat-strokeWidthThin) solid var(--webchat-colorNeutralStrokeDisabled); + color: var(--webchat-colorNeutralForegroundDisabled); + } + + .activity-button__icon { + height: 20px; + width: 20px; + + --webchat__component-icon--color: var(--webchat-colorNeutralForeground2); + + @media (forced-colors: active) { + --webchat__component-icon--color: buttonText; + } + } +} diff --git a/packages/fluent-theme/src/overrides/CollapsibleContent.module.css b/packages/fluent-theme/src/overrides/CollapsibleContent.module.css new file mode 100644 index 0000000000..dd2447455e --- /dev/null +++ b/packages/fluent-theme/src/overrides/CollapsibleContent.module.css @@ -0,0 +1,22 @@ +:global(.webchat-fluent) .part-grouping-decorator { + .stacked-layout .bubble .collapsible-content { + .collapsible-content__summary { + &:focus-visible { + border-radius: var(--webchat-borderRadiusSmall); + outline-offset: 4px; + outline: var(--webchat-strokeWidthThick) solid var(--webchat-colorStrokeFocus2); + } + } + + .collapsible-content__content { + margin-block: var(--webchat-part-grouping__collapsible-content--space-block-start-end); + } + } + &.variant-copilot { + &.part-grouping-decorator--from-bot { + .bubble .collapsible-content .collapsible-content__content { + margin-block: var(--webchat-spacingVerticalNone); + } + } + } +} diff --git a/packages/fluent-theme/src/overrides/CollapsibleGrouping.module.css b/packages/fluent-theme/src/overrides/CollapsibleGrouping.module.css new file mode 100644 index 0000000000..edfc210345 --- /dev/null +++ b/packages/fluent-theme/src/overrides/CollapsibleGrouping.module.css @@ -0,0 +1,156 @@ +:global(.webchat-fluent) .part-grouping-decorator { + .stacked-layout .collapsible-grouping__header { + justify-content: flex-start; + min-width: 0; + padding: var(--webchat-spacingVerticalNone) var(--webchat-spacingHorizontalNone); + place-self: start; + position: relative; + } + + .stacked-layout .collapsible-grouping { + display: grid; + + &.collapsible-grouping--open { + gap: var(--webchat-spacingVerticalM); + } + } + + .collapsible-grouping, + .collapsible-grouping__header { + border: none; + } + + .collapsible-grouping { + max-width: 100%; + } + &.variant-fluent { + &.part-grouping-decorator--group { + .collapsible-grouping { + anchor-name: --webchat-flair; + border-radius: var(--webchat-part-grouping__bubble--border-radius); + max-width: min(var(--webchat-part-grouping__bubble--max-width), 100%); + padding: var(--webchat-part-grouping__bubble--space-block) var(--webchat-part-grouping__bubble--space-inline); + position: relative; + transition: + background-color var(--webchat-durationNormal) var(--webchat-curveDecelerateMid), + box-shadow var(--webchat-durationNormal) var(--webchat-curveDecelerateMid); + } + + &:has(.collapsible-grouping--open) { + .collapsible-grouping { + background-color: var(--webchat-part-grouping__bubble--background-color); + box-shadow: var(--webchat-part-grouping__bubble--box-shadow); + } + } + } + } + &.variant-copilot { + &.part-grouping-decorator--group { + .collapsible-grouping__content { + border: var(--webchat-strokeWidthThin) solid transparent; + border-radius: var(--webchat-part-grouping__bubble--border-radius); + position: relative; + + @supports (anchor-name: --webchat-flair) { + anchor-name: --webchat-flair; + position: static; + } + } + + &:has(.collapsible-grouping--open) { + .collapsible-grouping__content { + background-color: var(--webchat-part-grouping__bubble--background-color); + border-color: var(--webchat-part-grouping__bubble--border-color); + } + } + } + + .stacked-layout:has(.collapsible-grouping) { + margin-inline: var(--webchat-spacingHorizontalNone); + } + + .collapsible-grouping { + margin-inline: var(--webchat-spacingHorizontalXS); + } + + .collapsible-grouping .collapsible-grouping__chevron, + .collapsible-content .collapsible-content__chevron { + transform: rotate(270deg); + } + + .collapsible-grouping--open .collapsible-grouping__chevron, + .collapsible-content[open] .collapsible-content__chevron { + transform: rotate(360deg); + } + + .collapsible-grouping__header { + margin-inline: calc(var(--webchat-spacingHorizontalXS) * -1); + } + + .collapsible-grouping__title { + color: var(--webchat-colorNeutralForeground2); + font-size: var(--webchat-fontSizeBase200); + font-weight: var(--webchat-fontWeightSemibold); + line-height: var(--webchat-lineHeightBase200); + } + + .collapsible-grouping__content { + anchor-name: --webchat-flair; + background: var(--webchat-colorNeutralBackground1); + background-clip: padding-box; + border-radius: var(--webchat-borderRadius2XLarge); + height: unset; + opacity: unset; + visibility: unset; + } + + .collapsible-grouping__list > * { + interpolate-size: allow-keywords; + transition: + margin calc(var(--webchat__transition-duration) / 2) var(--webchat__transition-easing), + height calc(var(--webchat__transition-duration) / 2) var(--webchat__transition-easing); + } + + .collapsible-grouping__content--open .collapsible-grouping__list > * { + /* We use animation so we can reset needed properties immediately upon switching classes */ + animation: collapsible-grouping__fade-in var(--webchat__transition-duration) + calc(var(--webchat__transition-duration) / 4) var(--webchat__transition-easing) both; + height: auto; + transition: + margin var(--webchat__transition-duration) var(--webchat__transition-easing), + height var(--webchat__transition-duration) var(--webchat__transition-easing); + } + + .collapsible-grouping__content:not(.collapsible-grouping__content--open) { + /* We use animation so we can reset needed properties immediately upon switching classes */ + animation: collapsible-grouping__fade-in var(--webchat__transition-duration) var(--webchat__transition-duration) + var(--webchat__transition-easing) both; + } + + .collapsible-grouping__content:not(.collapsible-grouping__content--open) .collapsible-grouping__list { + padding: var(--webchat-spacingVerticalNone) var(--webchat-spacingHorizontalNone); + } + } +} +/* Transcript focused content */ +:global(.webchat-fluent).theme .transcript-focus-area:focus-visible .transcript-focus-area__content--focused { + .collapsible-grouping::after { + border-radius: var(--webchat-part-grouping__bubble--border-radius); + border: none; + content: ''; + inset: 0; + isolation: isolate; + outline: var(--webchat-strokeWidthThick) solid var(--webchat-colorStrokeFocus2); + pointer-events: none; + position: absolute; + } +} + +/* Transcript focused content copilot variant */ +:global(.webchat-fluent).theme.variant-copilot + .transcript-focus-area:focus-visible + .transcript-focus-area__content--focused { + .collapsible-grouping::after { + inset: -4px; + } +} diff --git a/packages/fluent-theme/src/overrides/PartGroupingActivity.module.css b/packages/fluent-theme/src/overrides/PartGroupingActivity.module.css new file mode 100644 index 0000000000..87dcb0e5b6 --- /dev/null +++ b/packages/fluent-theme/src/overrides/PartGroupingActivity.module.css @@ -0,0 +1,6 @@ +:global(.webchat-fluent) .part-grouping-decorator { + .stacked-layout .collapsible-grouping__content .part-grouping-activity__activities { + --webchat--collapsible-grouping__list--gap: var(--webchat-spacingVerticalS); + padding: var(--webchat-spacingVerticalL) var(--webchat-spacingHorizontalL); + } +} diff --git a/packages/fluent-theme/src/private/fluentCSSModuleOverrides.ts b/packages/fluent-theme/src/private/fluentCSSModuleOverrides.ts index 0e7efcb2a9..5f37979050 100644 --- a/packages/fluent-theme/src/private/fluentCSSModuleOverrides.ts +++ b/packages/fluent-theme/src/private/fluentCSSModuleOverrides.ts @@ -5,24 +5,12 @@ // PartGroupingDecorator.module.css const partGroupingDecoratorOverrides: readonly string[] = Object.freeze([ - 'activity-button', - 'activity-button__text', 'adaptive-card-renderer', 'adaptive-card-renderer__action--selected', 'bubble', 'bubble__content', 'collapsible-content', - 'collapsible-content__chevron', 'collapsible-content__content', - 'collapsible-content__summary', - 'collapsible-grouping', - 'collapsible-grouping--open', - 'collapsible-grouping__chevron', - 'collapsible-grouping__content', - 'collapsible-grouping__content--open', - 'collapsible-grouping__header', - 'collapsible-grouping__list', - 'collapsible-grouping__title', 'link-definitions', 'link-definitions__badge', 'link-definitions__header', @@ -37,7 +25,6 @@ const partGroupingDecoratorOverrides: readonly string[] = Object.freeze([ 'link-definitions__list-item-text', 'link-definitions__message-sensitivity-label-text', 'link-definitions__open-in-new-window-icon', - 'part-grouping-activity__activities', 'render-markdown', 'stacked-layout', 'stacked-layout__attachment-row', @@ -50,13 +37,6 @@ const partGroupingDecoratorOverrides: readonly string[] = Object.freeze([ const sendBoxOverrides: readonly string[] = Object.freeze([]); // Theme.module.css -const themeOverrides: readonly string[] = Object.freeze([ - 'activity-button', - 'activity-button__icon', - 'animated-image-content__button', - 'collapsible-grouping', - 'transcript-focus-area', - 'transcript-focus-area__content--focused' -]); +const themeOverrides: readonly string[] = Object.freeze(['activity-button', 'animated-image-content__button']); export { partGroupingDecoratorOverrides, sendBoxOverrides, themeOverrides }; diff --git a/packages/fluent-theme/src/private/fluentCSSModuleTheme.ts b/packages/fluent-theme/src/private/fluentCSSModuleTheme.ts index 9d1ea54b0e..a55b9df379 100644 --- a/packages/fluent-theme/src/private/fluentCSSModuleTheme.ts +++ b/packages/fluent-theme/src/private/fluentCSSModuleTheme.ts @@ -4,11 +4,15 @@ import partGroupingDecoratorStyles from '../components/activity/PartGroupingDeco import themeStyles from '../components/theme/Theme.module.css'; import messageStatusLoaderOverrideStyles from '../overrides/MessageStatusLoader.module.css'; import bubbleOverrideStyles from '../overrides/Bubble.module.css'; +import activityButtonOverrideStyles from '../overrides/ActivityButton.module.css'; +import collapsibleGroupingOverrideStyles from '../overrides/CollapsibleGrouping.module.css'; +import partGroupingActivityOverrideStyles from '../overrides/PartGroupingActivity.module.css'; import stackedLayoutOverrideStyles from '../overrides/StackedLayout.module.css'; import componentIconOverrideStyles from '../overrides/ComponentIcon.module.css'; import carouselFilmStripOverrideStyles from '../overrides/CarouselFilmStrip.module.css'; import codeBlockContentOverrideStyles from '../overrides/CodeBlockContent.module.css'; import codeBlockCopyButtonOverrideStyles from '../overrides/CodeBlockCopyButton.module.css'; +import collapsibleContentOverrideStyles from '../overrides/CollapsibleContent.module.css'; import transcriptFocusOverrideStyles from '../overrides/TranscriptFocus.module.css'; import borderFlairOverrideStyles from '../overrides/BorderFlair.module.css'; import activityStatusOverrideStyles from '../overrides/ActivityStatus.module.css'; @@ -31,11 +35,15 @@ const fluentCSSModuleTheme = combineCSSModules( pickCSSModule(themeStyles, themeOverrides), messageStatusLoaderOverrideStyles, bubbleOverrideStyles, + activityButtonOverrideStyles, + collapsibleGroupingOverrideStyles, + partGroupingActivityOverrideStyles, stackedLayoutOverrideStyles, componentIconOverrideStyles, carouselFilmStripOverrideStyles, codeBlockContentOverrideStyles, codeBlockCopyButtonOverrideStyles, + collapsibleContentOverrideStyles, transcriptFocusOverrideStyles, borderFlairOverrideStyles, activityStatusOverrideStyles, From 78b3adae36bb5340d2af00dc1238c3624e68216b Mon Sep 17 00:00:00 2001 From: Eugene Olonov Date: Fri, 30 Oct 2026 17:49:59 -0700 Subject: [PATCH 17/24] Move Fluent's rules for text and Markdown to override modules Rules move verbatim from PartGroupingDecorator.module.css and Theme.module.css to overrides/LinkDefinitions.module.css, RenderMarkdown.module.css and TextContent.module.css. --- .../activity/PartGroupingDecorator.module.css | 286 ------------------ .../src/overrides/LinkDefinitions.module.css | 127 ++++++++ .../src/overrides/RenderMarkdown.module.css | 97 ++++++ .../src/overrides/TextContent.module.css | 53 ++++ .../src/private/fluentCSSModuleOverrides.ts | 24 +- .../src/private/fluentCSSModuleTheme.ts | 6 + 6 files changed, 284 insertions(+), 309 deletions(-) create mode 100644 packages/fluent-theme/src/overrides/LinkDefinitions.module.css create mode 100644 packages/fluent-theme/src/overrides/RenderMarkdown.module.css create mode 100644 packages/fluent-theme/src/overrides/TextContent.module.css diff --git a/packages/fluent-theme/src/components/activity/PartGroupingDecorator.module.css b/packages/fluent-theme/src/components/activity/PartGroupingDecorator.module.css index 6e7c1fa7a3..2dabacade8 100644 --- a/packages/fluent-theme/src/components/activity/PartGroupingDecorator.module.css +++ b/packages/fluent-theme/src/components/activity/PartGroupingDecorator.module.css @@ -42,282 +42,6 @@ } /* #endregion */ - /* #region Generated badge & attachments */ - .stacked-layout .bubble .text-content__generated-badge { - align-items: center; - align-self: flex-start; - background-color: var(--webchat-colorNeutralBackground5); - border-radius: var(--webchat-borderRadiusMedium); - box-sizing: border-box; - color: var(--webchat-colorNeutralForeground3); - cursor: default; - display: inline-flex; - font-size: var(--webchat-fontSizeBase100); - height: 16px; - line-height: var(--webchat-lineHeightBase100); - padding-inline: var(--webchat-spacingHorizontalXS); - } - /* #endregion */ - - /* #region Markdown & citations */ - .render-markdown a[href]:not(:global(.render-markdown__pure-identifier)) { - color: var(--webchat-colorBrandForegroundLink); - text-decoration-color: transparent; - - &:target { - color: var(--webchat-colorBrandForegroundLinkSelected); - } - - &:hover { - color: var(--webchat-colorBrandForegroundLinkHover); - text-decoration: underline 1px currentColor; - } - - &:active { - color: var(--webchat-colorBrandForegroundLinkPressed); - } - - &:focus-visible { - outline: none; - text-decoration: underline 1px double var(--webchat-colorStrokeFocus2); - } - - :global(.render-markdown__external-link-icon) { - background: currentColor; - -webkit-mask: var(--webchat__icon-url--external-link) no-repeat; - mask: var(--webchat__icon-url--external-link) no-repeat; - - @media (forced-colors: active) { - background: LinkText; - } - } - } - - .render-markdown :is(:global(.render-markdown__citation), :global(.render-markdown__pure-identifier)) { - align-items: center; - background-color: var(--webchat-colorNeutralBackground3); - border-radius: var(--webchat-borderRadiusMedium); - border: var(--webchat-strokeWidthThin) solid var(--webchat-colorNeutralStroke2); - box-sizing: border-box; - color: var(--webchat-colorNeutralForeground2); - display: inline-flex; - font-size: var(--webchat-fontSizeBase100); - font-weight: var(--webchat-fontWeightSemibold); - justify-content: center; - line-height: 12.4px; - margin-left: var(--webchat-spacingHorizontalXXS); - margin-right: var(--webchat-spacingHorizontalXXS); - min-height: 12.4px; - min-width: 14px; - text-decoration: none; - transition: all var(--webchat-durationNormal) var(--webchat-curveDecelerateMid); - vertical-align: calc((var(--webchat-lineHeightBase100) - var(--webchat-fontSizeBase100)) / 2); - - &:hover { - background-color: var(--webchat-colorBrandBackground2Hover); - border-color: var(--webchat-colorBrandStroke2Hover); - color: var(--webchat-colorBrandForeground2Hover); - cursor: pointer; - } - - &:hover:active { - background-color: var(--webchat-colorBrandBackground2Pressed); - border-color: var(--webchat-colorBrandStroke2Pressed); - color: var(--webchat-colorBrandForeground2Pressed); - } - - &::before, - &::after { - all: unset; - } - - &:has(:global(.render-markdown__external-link-icon)) { - padding-inline: 3px; - } - - :global(.render-markdown__external-link-icon) { - background: currentColor; - height: 0.7em; - -webkit-mask: var(--webchat__icon-url--external-link) no-repeat; - mask: var(--webchat__icon-url--external-link) no-repeat; - - @media (forced-colors: active) { - background: LinkText; - } - } - - @media (forced-colors: active) { - &, - &:hover, - &:hover:active { - border-color: LinkText; - color: LinkText; - } - } - } - /* #endregion */ - - /* #region Citation summary */ - .link-definitions__header { - border-radius: var(--webchat-borderRadiusMedium); - - .link-definitions__header-text { - color: var(--webchat-colorNeutralForeground3); - } - - .link-definitions__message-sensitivity-label-text { - color: var(--webchat-colorNeutralForeground4); - } - - .link-definitions__header-chevron { - color: var(--webchat-colorNeutralForeground3); - font-size: var(--webchat__font-size--small); - } - - &:focus-visible { - outline-offset: 1px; - outline: var(--webchat-strokeWidthThin) solid var(--webchat-colorStrokeFocus2); - } - } - - .link-definitions { - --webchat__citation__external-link--mask: var(--webchat-externalLink-mask); - --webchat__citation__link--max-width: var(--webchat-externalLink-maxWidth); - - .link-definitions__list { - color: var(--webchat__color--subtle); - display: flex; - flex-flow: row wrap; - gap: var(--webchat-spacingHorizontalS); - } - - .link-definitions__list-item { - border-radius: var(--webchat-borderRadiusMedium); - max-width: var(--webchat__citation__link--max-width); - } - - .link-definitions__list-item-box { - background-color: var(--webchat-colorNeutralBackground3); - border-radius: var(--webchat-borderRadiusMedium); - border: var(--webchat-strokeWidthThin) solid var(--webchat-colorNeutralStroke2); - box-sizing: border-box; - color: currentColor; - display: inline-flex; - font-size: var(--webchat__font-size--small); - height: 24px; - padding-inline-end: var(--webchat-spacingHorizontalS); - - /* Button chips use the link color like link chips; the badge, divider and text inherit it. */ - @media (forced-colors: active) { - border-color: LinkText; - color: LinkText; - } - } - - .link-definitions__list-item-body { - font-family: inherit; - gap: var(--webchat-spacingHorizontalSNudge); - min-width: 0; - padding: 0; - } - - .link-definitions__badge { - align-self: center; - background-color: transparent; - border-radius: 0; - border: none; - color: currentColor; - font-size: var(--webchat-fontSizeBase100); - font-weight: var(--webchat-fontWeightSemibold); - line-height: var(--webchat-lineHeightBase100); - margin: 0; - min-width: 20px; - padding: 0; - position: relative; - text-align: center; - } - - .link-definitions__badge::after { - border-right: var(--webchat-strokeWidthThin) solid var(--webchat-colorNeutralStroke2); - bottom: 0; - content: ''; - display: block; - height: 16px; - position: absolute; - right: 0; - top: 0; - width: 0; - } - - .link-definitions__list-item-main-text { - gap: var(--webchat-spacingHorizontalXS); - } - - .link-definitions__list-item-text { - color: currentColor; - text-decoration: none; - text-overflow: ellipsis; - } - - .link-definitions__list-item-main-text:has(.link-definitions__open-in-new-window-icon)::before { - align-self: center; - background: currentColor; - color: currentColor; - content: ''; - flex: none; - font-size: var(--webchat-fontSizeBase400); - height: 1em; - -webkit-mask: var(--webchat__citation__external-link--mask) no-repeat; - mask: var(--webchat__citation__external-link--mask) no-repeat; - padding: 0; - width: 1em; - - @media (forced-colors: active) { - background: LinkText; - } - } - - /* It seems Copilot does not show per-item sensitivity label, i.e. "General", etc. */ - .link-definitions__list-item-badge, - .link-definitions__open-in-new-window-icon { - display: none; - } - } - /* #endregion */ - - /* #region Header & collapsible grouping */ - - .stacked-layout .bubble .text-content { - font-size: var(--webchat__font-size--medium); - line-height: var(--webchat__line-height--medium); - min-height: auto; - padding: var(--webchat-part-grouping__text-content--space-block-inline); - - &:empty { - padding-block-end: 0; - } - - + .text-content { - margin-top: calc(var(--webchat-part-grouping__bubble--space-block) * -1); - } - } - /* #endregion */ - - /* #region Code blocks */ - /* #endregion */ - - /* #region Collapsible content */ - .stacked-layout .bubble .collapsible-content { - .collapsible-content__content { - .stacked-layout__attachment-row { - .text-content { - padding-block: 0; - } - } - } - } - /* #endregion */ - /* #region Group modifier */ &.part-grouping-decorator--group { --webchat-part-grouping__bubble-content--background: unset; @@ -436,16 +160,6 @@ --webchat-part-grouping__message-status--space-block: var(--webchat-spacingVerticalNone); --webchat-part-grouping__text-content--space-block-inline: var(--webchat-spacingVerticalNone) var(--webchat-spacingHorizontalNone); - - .bubble .bubble__content { - .stacked-layout__bubble .text-content { - color: var(--webchat-colorNeutralForeground3); - } - } - } - - .bubble .text-content .text-content__generated-badge { - display: none; } } /* #endregion */ diff --git a/packages/fluent-theme/src/overrides/LinkDefinitions.module.css b/packages/fluent-theme/src/overrides/LinkDefinitions.module.css new file mode 100644 index 0000000000..43619cef10 --- /dev/null +++ b/packages/fluent-theme/src/overrides/LinkDefinitions.module.css @@ -0,0 +1,127 @@ +:global(.webchat-fluent) .part-grouping-decorator { + .link-definitions__header { + border-radius: var(--webchat-borderRadiusMedium); + + .link-definitions__header-text { + color: var(--webchat-colorNeutralForeground3); + } + + .link-definitions__message-sensitivity-label-text { + color: var(--webchat-colorNeutralForeground4); + } + + .link-definitions__header-chevron { + color: var(--webchat-colorNeutralForeground3); + font-size: var(--webchat__font-size--small); + } + + &:focus-visible { + outline-offset: 1px; + outline: var(--webchat-strokeWidthThin) solid var(--webchat-colorStrokeFocus2); + } + } + + .link-definitions { + --webchat__citation__external-link--mask: var(--webchat-externalLink-mask); + --webchat__citation__link--max-width: var(--webchat-externalLink-maxWidth); + + .link-definitions__list { + color: var(--webchat__color--subtle); + display: flex; + flex-flow: row wrap; + gap: var(--webchat-spacingHorizontalS); + } + + .link-definitions__list-item { + border-radius: var(--webchat-borderRadiusMedium); + max-width: var(--webchat__citation__link--max-width); + } + + .link-definitions__list-item-box { + background-color: var(--webchat-colorNeutralBackground3); + border-radius: var(--webchat-borderRadiusMedium); + border: var(--webchat-strokeWidthThin) solid var(--webchat-colorNeutralStroke2); + box-sizing: border-box; + color: currentColor; + display: inline-flex; + font-size: var(--webchat__font-size--small); + height: 24px; + padding-inline-end: var(--webchat-spacingHorizontalS); + + /* Button chips use the link color like link chips; the badge, divider and text inherit it. */ + @media (forced-colors: active) { + border-color: LinkText; + color: LinkText; + } + } + + .link-definitions__list-item-body { + font-family: inherit; + gap: var(--webchat-spacingHorizontalSNudge); + min-width: 0; + padding: 0; + } + + .link-definitions__badge { + align-self: center; + background-color: transparent; + border-radius: 0; + border: none; + color: currentColor; + font-size: var(--webchat-fontSizeBase100); + font-weight: var(--webchat-fontWeightSemibold); + line-height: var(--webchat-lineHeightBase100); + margin: 0; + min-width: 20px; + padding: 0; + position: relative; + text-align: center; + } + + .link-definitions__badge::after { + border-right: var(--webchat-strokeWidthThin) solid var(--webchat-colorNeutralStroke2); + bottom: 0; + content: ''; + display: block; + height: 16px; + position: absolute; + right: 0; + top: 0; + width: 0; + } + + .link-definitions__list-item-main-text { + gap: var(--webchat-spacingHorizontalXS); + } + + .link-definitions__list-item-text { + color: currentColor; + text-decoration: none; + text-overflow: ellipsis; + } + + .link-definitions__list-item-main-text:has(.link-definitions__open-in-new-window-icon)::before { + align-self: center; + background: currentColor; + color: currentColor; + content: ''; + flex: none; + font-size: var(--webchat-fontSizeBase400); + height: 1em; + -webkit-mask: var(--webchat__citation__external-link--mask) no-repeat; + mask: var(--webchat__citation__external-link--mask) no-repeat; + padding: 0; + width: 1em; + + @media (forced-colors: active) { + background: LinkText; + } + } + + /* It seems Copilot does not show per-item sensitivity label, i.e. "General", etc. */ + .link-definitions__list-item-badge, + .link-definitions__open-in-new-window-icon { + display: none; + } + } +} diff --git a/packages/fluent-theme/src/overrides/RenderMarkdown.module.css b/packages/fluent-theme/src/overrides/RenderMarkdown.module.css new file mode 100644 index 0000000000..867c9bcd63 --- /dev/null +++ b/packages/fluent-theme/src/overrides/RenderMarkdown.module.css @@ -0,0 +1,97 @@ +:global(.webchat-fluent) .part-grouping-decorator { + .render-markdown a[href]:not(:global(.render-markdown__pure-identifier)) { + color: var(--webchat-colorBrandForegroundLink); + text-decoration-color: transparent; + + &:target { + color: var(--webchat-colorBrandForegroundLinkSelected); + } + + &:hover { + color: var(--webchat-colorBrandForegroundLinkHover); + text-decoration: underline 1px currentColor; + } + + &:active { + color: var(--webchat-colorBrandForegroundLinkPressed); + } + + &:focus-visible { + outline: none; + text-decoration: underline 1px double var(--webchat-colorStrokeFocus2); + } + + :global(.render-markdown__external-link-icon) { + background: currentColor; + -webkit-mask: var(--webchat__icon-url--external-link) no-repeat; + mask: var(--webchat__icon-url--external-link) no-repeat; + + @media (forced-colors: active) { + background: LinkText; + } + } + } + + .render-markdown :is(:global(.render-markdown__citation), :global(.render-markdown__pure-identifier)) { + align-items: center; + background-color: var(--webchat-colorNeutralBackground3); + border-radius: var(--webchat-borderRadiusMedium); + border: var(--webchat-strokeWidthThin) solid var(--webchat-colorNeutralStroke2); + box-sizing: border-box; + color: var(--webchat-colorNeutralForeground2); + display: inline-flex; + font-size: var(--webchat-fontSizeBase100); + font-weight: var(--webchat-fontWeightSemibold); + justify-content: center; + line-height: 12.4px; + margin-left: var(--webchat-spacingHorizontalXXS); + margin-right: var(--webchat-spacingHorizontalXXS); + min-height: 12.4px; + min-width: 14px; + text-decoration: none; + transition: all var(--webchat-durationNormal) var(--webchat-curveDecelerateMid); + vertical-align: calc((var(--webchat-lineHeightBase100) - var(--webchat-fontSizeBase100)) / 2); + + &:hover { + background-color: var(--webchat-colorBrandBackground2Hover); + border-color: var(--webchat-colorBrandStroke2Hover); + color: var(--webchat-colorBrandForeground2Hover); + cursor: pointer; + } + + &:hover:active { + background-color: var(--webchat-colorBrandBackground2Pressed); + border-color: var(--webchat-colorBrandStroke2Pressed); + color: var(--webchat-colorBrandForeground2Pressed); + } + + &::before, + &::after { + all: unset; + } + + &:has(:global(.render-markdown__external-link-icon)) { + padding-inline: 3px; + } + + :global(.render-markdown__external-link-icon) { + background: currentColor; + height: 0.7em; + -webkit-mask: var(--webchat__icon-url--external-link) no-repeat; + mask: var(--webchat__icon-url--external-link) no-repeat; + + @media (forced-colors: active) { + background: LinkText; + } + } + + @media (forced-colors: active) { + &, + &:hover, + &:hover:active { + border-color: LinkText; + color: LinkText; + } + } + } +} diff --git a/packages/fluent-theme/src/overrides/TextContent.module.css b/packages/fluent-theme/src/overrides/TextContent.module.css new file mode 100644 index 0000000000..10b00e7da6 --- /dev/null +++ b/packages/fluent-theme/src/overrides/TextContent.module.css @@ -0,0 +1,53 @@ +:global(.webchat-fluent) .part-grouping-decorator { + .stacked-layout .bubble .text-content__generated-badge { + align-items: center; + align-self: flex-start; + background-color: var(--webchat-colorNeutralBackground5); + border-radius: var(--webchat-borderRadiusMedium); + box-sizing: border-box; + color: var(--webchat-colorNeutralForeground3); + cursor: default; + display: inline-flex; + font-size: var(--webchat-fontSizeBase100); + height: 16px; + line-height: var(--webchat-lineHeightBase100); + padding-inline: var(--webchat-spacingHorizontalXS); + } + + .stacked-layout .bubble .text-content { + font-size: var(--webchat__font-size--medium); + line-height: var(--webchat__line-height--medium); + min-height: auto; + padding: var(--webchat-part-grouping__text-content--space-block-inline); + + &:empty { + padding-block-end: 0; + } + + + .text-content { + margin-top: calc(var(--webchat-part-grouping__bubble--space-block) * -1); + } + } + .stacked-layout .bubble .collapsible-content { + .collapsible-content__content { + .stacked-layout__attachment-row { + .text-content { + padding-block: 0; + } + } + } + } + &.variant-copilot { + &.part-grouping-decorator--group { + .bubble .bubble__content { + .stacked-layout__bubble .text-content { + color: var(--webchat-colorNeutralForeground3); + } + } + } + + .bubble .text-content .text-content__generated-badge { + display: none; + } + } +} diff --git a/packages/fluent-theme/src/private/fluentCSSModuleOverrides.ts b/packages/fluent-theme/src/private/fluentCSSModuleOverrides.ts index 5f37979050..769e4cce27 100644 --- a/packages/fluent-theme/src/private/fluentCSSModuleOverrides.ts +++ b/packages/fluent-theme/src/private/fluentCSSModuleOverrides.ts @@ -8,29 +8,7 @@ const partGroupingDecoratorOverrides: readonly string[] = Object.freeze([ 'adaptive-card-renderer', 'adaptive-card-renderer__action--selected', 'bubble', - 'bubble__content', - 'collapsible-content', - 'collapsible-content__content', - 'link-definitions', - 'link-definitions__badge', - 'link-definitions__header', - 'link-definitions__header-chevron', - 'link-definitions__header-text', - 'link-definitions__list', - 'link-definitions__list-item', - 'link-definitions__list-item-badge', - 'link-definitions__list-item-body', - 'link-definitions__list-item-box', - 'link-definitions__list-item-main-text', - 'link-definitions__list-item-text', - 'link-definitions__message-sensitivity-label-text', - 'link-definitions__open-in-new-window-icon', - 'render-markdown', - 'stacked-layout', - 'stacked-layout__attachment-row', - 'stacked-layout__bubble', - 'text-content', - 'text-content__generated-badge' + 'bubble__content' ]); // SendBox.module.css diff --git a/packages/fluent-theme/src/private/fluentCSSModuleTheme.ts b/packages/fluent-theme/src/private/fluentCSSModuleTheme.ts index a55b9df379..1e92928277 100644 --- a/packages/fluent-theme/src/private/fluentCSSModuleTheme.ts +++ b/packages/fluent-theme/src/private/fluentCSSModuleTheme.ts @@ -2,8 +2,11 @@ import { combineCSSModules } from 'botframework-webchat/internal.js'; import partGroupingDecoratorStyles from '../components/activity/PartGroupingDecorator.module.css'; import themeStyles from '../components/theme/Theme.module.css'; +import textContentOverrideStyles from '../overrides/TextContent.module.css'; import messageStatusLoaderOverrideStyles from '../overrides/MessageStatusLoader.module.css'; import bubbleOverrideStyles from '../overrides/Bubble.module.css'; +import renderMarkdownOverrideStyles from '../overrides/RenderMarkdown.module.css'; +import linkDefinitionsOverrideStyles from '../overrides/LinkDefinitions.module.css'; import activityButtonOverrideStyles from '../overrides/ActivityButton.module.css'; import collapsibleGroupingOverrideStyles from '../overrides/CollapsibleGrouping.module.css'; import partGroupingActivityOverrideStyles from '../overrides/PartGroupingActivity.module.css'; @@ -33,8 +36,11 @@ import pickCSSModule from './pickCSSModule'; const fluentCSSModuleTheme = combineCSSModules( pickCSSModule(partGroupingDecoratorStyles, partGroupingDecoratorOverrides), pickCSSModule(themeStyles, themeOverrides), + textContentOverrideStyles, messageStatusLoaderOverrideStyles, bubbleOverrideStyles, + renderMarkdownOverrideStyles, + linkDefinitionsOverrideStyles, activityButtonOverrideStyles, collapsibleGroupingOverrideStyles, partGroupingActivityOverrideStyles, From 4a79ead27037c018393f95d8eead985bd5973471 Mon Sep 17 00:00:00 2001 From: Eugene Olonov Date: Fri, 30 Oct 2026 17:50:04 -0700 Subject: [PATCH 18/24] Move Fluent's rules for Adaptive Cards and animated images to override modules Rules move verbatim from PartGroupingDecorator.module.css and Theme.module.css to overrides/AdaptiveCardRenderer.module.css and AnimatedImageContent.module.css. --- .../activity/PartGroupingDecorator.module.css | 66 ----------------- .../src/components/theme/Theme.module.css | 9 --- .../overrides/AdaptiveCardRenderer.module.css | 71 +++++++++++++++++++ .../overrides/AnimatedImageContent.module.css | 8 +++ .../src/private/fluentCSSModuleOverrides.ts | 9 +-- .../src/private/fluentCSSModuleTheme.ts | 10 ++- 6 files changed, 85 insertions(+), 88 deletions(-) create mode 100644 packages/fluent-theme/src/overrides/AdaptiveCardRenderer.module.css create mode 100644 packages/fluent-theme/src/overrides/AnimatedImageContent.module.css diff --git a/packages/fluent-theme/src/components/activity/PartGroupingDecorator.module.css b/packages/fluent-theme/src/components/activity/PartGroupingDecorator.module.css index 2dabacade8..0480b19afd 100644 --- a/packages/fluent-theme/src/components/activity/PartGroupingDecorator.module.css +++ b/packages/fluent-theme/src/components/activity/PartGroupingDecorator.module.css @@ -88,72 +88,6 @@ --webchat-part-grouping__code-block--space-inline: var(--webchat-spacingHorizontalNone); margin-inline-end: var(--webchat-spacingHorizontalSNudge); - - .bubble { - .bubble__content .adaptive-card-renderer > :global(.ac-adaptiveCard) { - font-family: var(--webchat-fontFamilyBase); - /* Remove double padding for adaptive cards inside bubble */ - padding: unset !important; - - :global(.ac-textBlock) { - font-family: var(--webchat-fontFamilyBase) !important; - } - - :global(.ac-container:not([style*='background-color:']) .ac-textBlock), - > :global(.ac-textBlock) { - color: currentColor !important; - } - - :global(.ac-pushButton) { - background-color: var(--webchat-colorNeutralBackground1); - color: var(--webchat-colorNeutralForeground1); - border: var(--webchat-strokeWidthThin) solid var(--webchat-colorNeutralStroke1); - border-radius: var(--webchat-borderRadiusLarge); - transition: all var(--webchat-durationNormal) var(--webchat-curveDecelerateMid); - } - - :global(.ac-pushButton:hover) { - background-color: var(--webchat-colorNeutralBackground1Hover); - border-color: var(--webchat-colorNeutralStroke1Hover); - color: var(--webchat-colorNeutralForeground1Hover); - cursor: pointer; - } - - :global(.ac-pushButton:active), - :global(.ac-pushButton).adaptive-card-renderer__action--selected { - background-color: var(--webchat-colorNeutralBackground1Pressed); - border-color: var(--webchat-colorNeutralStroke1Pressed); - color: var(--webchat-colorNeutralForeground1Pressed); - outline-style: none; - } - - :global(.ac-pushButton:focus-visible) { - border-color: var(--webchat-colorStrokeFocus2); - border-width: var(--webchat-strokeWidthThin); - outline: var(--webchat-strokeWidthThick) solid var(--webchat-colorTransparentStroke); - box-shadow: 0 0 0 var(--webchat-strokeWidthThin) var(--webchat-colorStrokeFocus2) inset; - } - - :global(.ac-pushButton.primary) { - background-color: var(--webchat-colorBrandBackground); - border-color: transparent; - color: var(--webchat-colorNeutralForegroundOnBrand); - } - - :global(.ac-pushButton.primary:hover) { - background-color: var(--webchat-colorBrandBackgroundHover); - border-color: transparent; - color: var(--webchat-colorNeutralForegroundOnBrand); - } - - :global(.ac-pushButton.primary:active), - :global(.ac-pushButton):global(.primary).adaptive-card-renderer__action--selected { - background-color: var(--webchat-colorBrandBackgroundPressed); - border-color: transparent; - color: var(--webchat-colorNeutralForegroundOnBrand); - } - } - } } &.part-grouping-decorator--group { diff --git a/packages/fluent-theme/src/components/theme/Theme.module.css b/packages/fluent-theme/src/components/theme/Theme.module.css index 0f9958a315..ebb0305177 100644 --- a/packages/fluent-theme/src/components/theme/Theme.module.css +++ b/packages/fluent-theme/src/components/theme/Theme.module.css @@ -299,15 +299,6 @@ } } -/* Pause button over animated images: a band in the button background around the focus indicator keeps it visible over any image */ -:global(.webchat-fluent).theme .activity-button.animated-image-content__button:focus-visible::after { - outline: var(--webchat-strokeWidthThick) solid var(--webchat-colorNeutralBackground1); - - @media (forced-colors: active) { - outline-color: ButtonFace; - } -} - /* Tooltip */ :global(.webchat-fluent).theme :global(.webchat__tooltip) { --webchat__tooltip-background: var(--tooltip-background, var(--webchat-colorNeutralBackground1)); diff --git a/packages/fluent-theme/src/overrides/AdaptiveCardRenderer.module.css b/packages/fluent-theme/src/overrides/AdaptiveCardRenderer.module.css new file mode 100644 index 0000000000..cc8043e7b4 --- /dev/null +++ b/packages/fluent-theme/src/overrides/AdaptiveCardRenderer.module.css @@ -0,0 +1,71 @@ +:global(.webchat-fluent) .part-grouping-decorator { + &.variant-copilot { + &.part-grouping-decorator--from-bot { + .bubble { + .bubble__content .adaptive-card-renderer > :global(.ac-adaptiveCard) { + font-family: var(--webchat-fontFamilyBase); + /* Remove double padding for adaptive cards inside bubble */ + padding: unset !important; + + :global(.ac-textBlock) { + font-family: var(--webchat-fontFamilyBase) !important; + } + + :global(.ac-container:not([style*='background-color:']) .ac-textBlock), + > :global(.ac-textBlock) { + color: currentColor !important; + } + + :global(.ac-pushButton) { + background-color: var(--webchat-colorNeutralBackground1); + color: var(--webchat-colorNeutralForeground1); + border: var(--webchat-strokeWidthThin) solid var(--webchat-colorNeutralStroke1); + border-radius: var(--webchat-borderRadiusLarge); + transition: all var(--webchat-durationNormal) var(--webchat-curveDecelerateMid); + } + + :global(.ac-pushButton:hover) { + background-color: var(--webchat-colorNeutralBackground1Hover); + border-color: var(--webchat-colorNeutralStroke1Hover); + color: var(--webchat-colorNeutralForeground1Hover); + cursor: pointer; + } + + :global(.ac-pushButton:active), + :global(.ac-pushButton).adaptive-card-renderer__action--selected { + background-color: var(--webchat-colorNeutralBackground1Pressed); + border-color: var(--webchat-colorNeutralStroke1Pressed); + color: var(--webchat-colorNeutralForeground1Pressed); + outline-style: none; + } + + :global(.ac-pushButton:focus-visible) { + border-color: var(--webchat-colorStrokeFocus2); + border-width: var(--webchat-strokeWidthThin); + outline: var(--webchat-strokeWidthThick) solid var(--webchat-colorTransparentStroke); + box-shadow: 0 0 0 var(--webchat-strokeWidthThin) var(--webchat-colorStrokeFocus2) inset; + } + + :global(.ac-pushButton.primary) { + background-color: var(--webchat-colorBrandBackground); + border-color: transparent; + color: var(--webchat-colorNeutralForegroundOnBrand); + } + + :global(.ac-pushButton.primary:hover) { + background-color: var(--webchat-colorBrandBackgroundHover); + border-color: transparent; + color: var(--webchat-colorNeutralForegroundOnBrand); + } + + :global(.ac-pushButton.primary:active), + :global(.ac-pushButton):global(.primary).adaptive-card-renderer__action--selected { + background-color: var(--webchat-colorBrandBackgroundPressed); + border-color: transparent; + color: var(--webchat-colorNeutralForegroundOnBrand); + } + } + } + } + } +} diff --git a/packages/fluent-theme/src/overrides/AnimatedImageContent.module.css b/packages/fluent-theme/src/overrides/AnimatedImageContent.module.css new file mode 100644 index 0000000000..c629f32e57 --- /dev/null +++ b/packages/fluent-theme/src/overrides/AnimatedImageContent.module.css @@ -0,0 +1,8 @@ +/* Pause button over animated images: a band in the button background around the focus indicator keeps it visible over any image */ +:global(.webchat-fluent).theme .activity-button.animated-image-content__button:focus-visible::after { + outline: var(--webchat-strokeWidthThick) solid var(--webchat-colorNeutralBackground1); + + @media (forced-colors: active) { + outline-color: ButtonFace; + } +} diff --git a/packages/fluent-theme/src/private/fluentCSSModuleOverrides.ts b/packages/fluent-theme/src/private/fluentCSSModuleOverrides.ts index 769e4cce27..a3956f30e0 100644 --- a/packages/fluent-theme/src/private/fluentCSSModuleOverrides.ts +++ b/packages/fluent-theme/src/private/fluentCSSModuleOverrides.ts @@ -4,17 +4,12 @@ // fluentCSSModuleOverrides.spec.ts checks these lists against the CSS. // PartGroupingDecorator.module.css -const partGroupingDecoratorOverrides: readonly string[] = Object.freeze([ - 'adaptive-card-renderer', - 'adaptive-card-renderer__action--selected', - 'bubble', - 'bubble__content' -]); +const partGroupingDecoratorOverrides: readonly string[] = Object.freeze([]); // SendBox.module.css const sendBoxOverrides: readonly string[] = Object.freeze([]); // Theme.module.css -const themeOverrides: readonly string[] = Object.freeze(['activity-button', 'animated-image-content__button']); +const themeOverrides: readonly string[] = Object.freeze([]); export { partGroupingDecoratorOverrides, sendBoxOverrides, themeOverrides }; diff --git a/packages/fluent-theme/src/private/fluentCSSModuleTheme.ts b/packages/fluent-theme/src/private/fluentCSSModuleTheme.ts index 1e92928277..0ccd80a33b 100644 --- a/packages/fluent-theme/src/private/fluentCSSModuleTheme.ts +++ b/packages/fluent-theme/src/private/fluentCSSModuleTheme.ts @@ -1,7 +1,5 @@ import { combineCSSModules } from 'botframework-webchat/internal.js'; -import partGroupingDecoratorStyles from '../components/activity/PartGroupingDecorator.module.css'; -import themeStyles from '../components/theme/Theme.module.css'; import textContentOverrideStyles from '../overrides/TextContent.module.css'; import messageStatusLoaderOverrideStyles from '../overrides/MessageStatusLoader.module.css'; import bubbleOverrideStyles from '../overrides/Bubble.module.css'; @@ -18,6 +16,7 @@ import codeBlockCopyButtonOverrideStyles from '../overrides/CodeBlockCopyButton. import collapsibleContentOverrideStyles from '../overrides/CollapsibleContent.module.css'; import transcriptFocusOverrideStyles from '../overrides/TranscriptFocus.module.css'; import borderFlairOverrideStyles from '../overrides/BorderFlair.module.css'; +import adaptiveCardRendererOverrideStyles from '../overrides/AdaptiveCardRenderer.module.css'; import activityStatusOverrideStyles from '../overrides/ActivityStatus.module.css'; import attachmentBarOverrideStyles from '../overrides/AttachmentBar.module.css'; import attachmentBarItemOverrideStyles from '../overrides/AttachmentBarItem.module.css'; @@ -25,17 +24,14 @@ import filePreviewOverrideStyles from '../overrides/FilePreview.module.css'; import textAreaOverrideStyles from '../overrides/TextArea.module.css'; import codeBlockOverrideStyles from '../overrides/CodeBlock.module.css'; import viewCodeDialogOverrideStyles from '../overrides/ViewCodeDialog.module.css'; +import animatedImageContentOverrideStyles from '../overrides/AnimatedImageContent.module.css'; import thumbButtonOverrideStyles from '../overrides/ThumbButton.module.css'; import feedbackFormOverrideStyles from '../overrides/FeedbackForm.module.css'; -import { partGroupingDecoratorOverrides, themeOverrides } from './fluentCSSModuleOverrides'; -import pickCSSModule from './pickCSSModule'; // Fluent's class names for the Web Chat elements it styles, for in FluentThemeProvider. // Each override module mirrors one Web Chat CSS module and is passed whole, in the order its rules had in // Fluent's CSS, to keep the cascade. Built once, so every useStyles() below the composer gets a stable theme. const fluentCSSModuleTheme = combineCSSModules( - pickCSSModule(partGroupingDecoratorStyles, partGroupingDecoratorOverrides), - pickCSSModule(themeStyles, themeOverrides), textContentOverrideStyles, messageStatusLoaderOverrideStyles, bubbleOverrideStyles, @@ -52,6 +48,7 @@ const fluentCSSModuleTheme = combineCSSModules( collapsibleContentOverrideStyles, transcriptFocusOverrideStyles, borderFlairOverrideStyles, + adaptiveCardRendererOverrideStyles, activityStatusOverrideStyles, attachmentBarOverrideStyles, attachmentBarItemOverrideStyles, @@ -59,6 +56,7 @@ const fluentCSSModuleTheme = combineCSSModules( textAreaOverrideStyles, codeBlockOverrideStyles, viewCodeDialogOverrideStyles, + animatedImageContentOverrideStyles, thumbButtonOverrideStyles, feedbackFormOverrideStyles ); From 518cd05f6c0fd01d1ad1278eb0a01749742bc3d4 Mon Sep 17 00:00:00 2001 From: Eugene Olonov Date: Fri, 30 Oct 2026 17:53:12 -0700 Subject: [PATCH 19/24] Drop the lists of Web Chat local names and pickCSSModule, now that every Fluent override is in an override module fluentCSSModuleTheme.spec.ts checks that only override modules use Web Chat local names, that each mirrors a Web Chat CSS module, and that FluentThemeProvider passes each of them. --- .../src/private/fluentCSSModuleOverrides.ts | 15 ---- ...s.spec.ts => fluentCSSModuleTheme.spec.ts} | 82 ++++--------------- .../src/private/pickCSSModule.spec.ts | 35 -------- .../fluent-theme/src/private/pickCSSModule.ts | 22 ----- 4 files changed, 17 insertions(+), 137 deletions(-) delete mode 100644 packages/fluent-theme/src/private/fluentCSSModuleOverrides.ts rename packages/fluent-theme/src/private/{fluentCSSModuleOverrides.spec.ts => fluentCSSModuleTheme.spec.ts} (58%) delete mode 100644 packages/fluent-theme/src/private/pickCSSModule.spec.ts delete mode 100644 packages/fluent-theme/src/private/pickCSSModule.ts diff --git a/packages/fluent-theme/src/private/fluentCSSModuleOverrides.ts b/packages/fluent-theme/src/private/fluentCSSModuleOverrides.ts deleted file mode 100644 index a3956f30e0..0000000000 --- a/packages/fluent-theme/src/private/fluentCSSModuleOverrides.ts +++ /dev/null @@ -1,15 +0,0 @@ -// Local names of Web Chat CSS modules that each Fluent CSS module styles, kept sorted. -// FluentThemeProvider passes Fluent's class names for them to Web Chat's useStyles() through , -// so Fluent rules select them as local names (`.thumb-button`), not as readable class names (`:global(.thumb-button)`). -// fluentCSSModuleOverrides.spec.ts checks these lists against the CSS. - -// PartGroupingDecorator.module.css -const partGroupingDecoratorOverrides: readonly string[] = Object.freeze([]); - -// SendBox.module.css -const sendBoxOverrides: readonly string[] = Object.freeze([]); - -// Theme.module.css -const themeOverrides: readonly string[] = Object.freeze([]); - -export { partGroupingDecoratorOverrides, sendBoxOverrides, themeOverrides }; diff --git a/packages/fluent-theme/src/private/fluentCSSModuleOverrides.spec.ts b/packages/fluent-theme/src/private/fluentCSSModuleTheme.spec.ts similarity index 58% rename from packages/fluent-theme/src/private/fluentCSSModuleOverrides.spec.ts rename to packages/fluent-theme/src/private/fluentCSSModuleTheme.spec.ts index 943b5aaa0f..f51acf45cd 100644 --- a/packages/fluent-theme/src/private/fluentCSSModuleOverrides.spec.ts +++ b/packages/fluent-theme/src/private/fluentCSSModuleTheme.spec.ts @@ -6,8 +6,6 @@ import { readdirSync, readFileSync } from 'fs'; import { transform } from 'lightningcss'; import { basename, join, relative, sep } from 'path'; -import { partGroupingDecoratorOverrides, sendBoxOverrides, themeOverrides } from './fluentCSSModuleOverrides'; - const PACKAGES_PATH = join(__dirname, '../../..'); const listCSSModules = (packageName: string): readonly string[] => @@ -70,81 +68,35 @@ const fluentLocalNames = new Set( // eslint-disable-next-line security/detect-non-literal-fs-filename const FLUENT_CSS_MODULE_THEME_SOURCE = readFileSync(join(__dirname, 'fluentCSSModuleTheme.ts'), 'utf8'); -const OVERRIDES_BY_FLUENT_CSS_MODULE: Readonly> = Object.freeze({ - 'fluent-theme/src/components/activity/PartGroupingDecorator.module.css': partGroupingDecoratorOverrides, - 'fluent-theme/src/components/sendBox/SendBox.module.css': sendBoxOverrides, - 'fluent-theme/src/components/theme/Theme.module.css': themeOverrides -}); - -// Fluent's own classes that happen to share a name with a Web Chat class, not overrides. -const FLUENT_OWN_LOCAL_NAMES: ReadonlySet = Object.freeze( +// FluentIcon's own classes, which happen to share a name with a Web Chat class. +const FLUENT_ICON_LOCAL_NAMES: ReadonlySet = Object.freeze( new Set(['appearance--text', 'icon--attachment', 'icon--microphone', 'icon--send']) ); scenario( - 'Fluent overrides of Web Chat CSS modules', + 'Fluent CSS module theme', bdd => { - bdd - .given('the local names that FluentThemeProvider passes through the CSS module theme', () => - Object.entries(OVERRIDES_BY_FLUENT_CSS_MODULE).map(([path, localNames]) => ({ - localNames, - path: join(PACKAGES_PATH, path) - })) - ) - .when('reading the Fluent CSS modules', overrides => - overrides.map(({ localNames, path }) => ({ - fluentLocalNames: getLocalNames(path), - globalClassNames: getGlobalClassNames(path), - localNames, - path - })) - ) - .then('every local name should be defined by the Fluent CSS module', (_, results) => - results.forEach(({ fluentLocalNames, localNames }) => - expect(fluentLocalNames).toEqual(expect.arrayContaining([...localNames])) - ) - ) - .and('every local name should be defined by a Web Chat CSS module', (_, results) => - results.forEach(({ localNames }) => - expect(localNames.filter(localName => !webChatLocalNames.has(localName))).toEqual([]) - ) - ) - .and('no local name should still be selected through :global()', (_, results) => - results.forEach(({ globalClassNames, localNames }) => - expect(localNames.filter(localName => globalClassNames.includes(localName))).toEqual([]) - ) - ) - .and('the lists should be sorted', (_, results) => - results.forEach(({ localNames }) => expect(localNames).toEqual([...localNames].sort())) - ); - bdd .given('every Fluent CSS module', () => listCSSModules('fluent-theme')) .when('reading its local names and the class names it selects through :global()', paths => - paths.map(path => { - const listedLocalNames = OVERRIDES_BY_FLUENT_CSS_MODULE[relative(PACKAGES_PATH, path)] ?? []; - - return { - path: relative(PACKAGES_PATH, path), - // A Web Chat local name in a Fluent rule only matches when it is listed for that module, - // or when the rule is in an override module, which is passed whole. - unlistedWebChatLocalNames: isOverrideModule(path) - ? [] - : getLocalNames(path).filter( - localName => - webChatLocalNames.has(localName) && - !listedLocalNames.includes(localName) && - !FLUENT_OWN_LOCAL_NAMES.has(localName) - ), - webChatGlobalClassNames: getGlobalClassNames(path).filter(className => webChatLocalNames.has(className)) - }; - }) + paths.map(path => ({ + path: relative(PACKAGES_PATH, path), + webChatGlobalClassNames: getGlobalClassNames(path).filter(className => webChatLocalNames.has(className)), + // A Web Chat local name in a Fluent rule only matches from an override module, which is passed whole. + webChatLocalNamesOutsideOverrides: isOverrideModule(path) + ? [] + : getLocalNames(path).filter( + localName => webChatLocalNames.has(localName) && !FLUENT_ICON_LOCAL_NAMES.has(localName) + ) + })) ) .then('none should select a local name of a Web Chat CSS module through :global()', (_, results) => expect(results.filter(({ webChatGlobalClassNames }) => webChatGlobalClassNames.length)).toEqual([]) ) - .and('every local name of a Web Chat CSS module should be listed', (_, results) => - expect(results.filter(({ unlistedWebChatLocalNames }) => unlistedWebChatLocalNames.length)).toEqual([]) + .and('only override modules should use local names of Web Chat CSS modules', (_, results) => + expect( + results.filter(({ webChatLocalNamesOutsideOverrides }) => webChatLocalNamesOutsideOverrides.length) + ).toEqual([]) ); bdd diff --git a/packages/fluent-theme/src/private/pickCSSModule.spec.ts b/packages/fluent-theme/src/private/pickCSSModule.spec.ts deleted file mode 100644 index 485774df72..0000000000 --- a/packages/fluent-theme/src/private/pickCSSModule.spec.ts +++ /dev/null @@ -1,35 +0,0 @@ -/// - -import * as JestGlobals from '@jest/globals'; -import { scenario } from '@testduet/given-when-then'; - -import pickCSSModule from './pickCSSModule'; - -scenario( - 'pickCSSModule', - bdd => { - bdd - .given('a CSS module with three local names', () => - Object.freeze({ - bubble: 'wA_bubble', - bubble__content: 'wA_bubble__content', - 'part-grouping-decorator': 'wA_part-grouping-decorator' - }) - ) - .when('picking two of them', cssModule => pickCSSModule(cssModule, ['bubble', 'bubble__content'])) - .then('should return the class names of the picked local names only', (_, result) => - expect(result).toStrictEqual({ bubble: 'wA_bubble', bubble__content: 'wA_bubble__content' }) - ) - .and('the result should be frozen', (_, result) => expect(Object.isFrozen(result)).toBe(true)); - - bdd - .given('a CSS module without "bubble"', () => - Object.freeze({ 'part-grouping-decorator': 'wA_part-grouping-decorator' }) - ) - .when('picking "bubble"', cssModule => () => pickCSSModule(cssModule, ['bubble'])) - .then('should throw', (_, pick) => - expect(pick).toThrow('The CSS module does not define the local name "bubble".') - ); - }, - JestGlobals -); diff --git a/packages/fluent-theme/src/private/pickCSSModule.ts b/packages/fluent-theme/src/private/pickCSSModule.ts deleted file mode 100644 index 4895301337..0000000000 --- a/packages/fluent-theme/src/private/pickCSSModule.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { parse, string } from 'valibot'; - -/** - * Picks the class names of the given local names from a CSS module, for `CSSModuleThemeComposer`. - * - * Throws if the CSS module does not define one of the local names. - */ -export default function pickCSSModule( - cssModule: CSSModuleClasses, - localNames: readonly string[] -): Readonly> { - const classNamesMap = new Map(Object.entries(cssModule)); - - return Object.freeze( - Object.fromEntries( - localNames.map(localName => [ - localName, - parse(string(`The CSS module does not define the local name "${localName}".`), classNamesMap.get(localName)) - ]) - ) - ); -} From ba8d210d635928843b60a7dc3c92fbf6208598c2 Mon Sep 17 00:00:00 2001 From: Eugene Olonov Date: Fri, 30 Oct 2026 18:31:51 -0700 Subject: [PATCH 20/24] Write the ButtonText system color in TitleCase in the activity button override --- packages/fluent-theme/src/overrides/ActivityButton.module.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/fluent-theme/src/overrides/ActivityButton.module.css b/packages/fluent-theme/src/overrides/ActivityButton.module.css index 8845a5a0ba..2bfd5abdb9 100644 --- a/packages/fluent-theme/src/overrides/ActivityButton.module.css +++ b/packages/fluent-theme/src/overrides/ActivityButton.module.css @@ -69,7 +69,7 @@ --webchat__component-icon--color: var(--webchat-colorNeutralForeground2); @media (forced-colors: active) { - --webchat__component-icon--color: buttonText; + --webchat__component-icon--color: ButtonText; } } } From 8a8dd780d6599503d8e34b7805feaf76f6ef7972 Mon Sep 17 00:00:00 2001 From: Eugene Olonov Date: Fri, 30 Oct 2026 19:03:18 -0700 Subject: [PATCH 21/24] Keep Fluent's scrollbar rules together in the code block content override The thumb rule for hover and a focused text area input had landed in its own TextArea override, which loads after the rest of the block: with the same specificity as the thumb hover rule, it won. The block is whole again, in its original order. --- .../src/overrides/CodeBlockContent.module.css | 10 ++++++++++ .../src/overrides/TextArea.module.css | 17 ----------------- .../src/private/fluentCSSModuleTheme.ts | 2 -- 3 files changed, 10 insertions(+), 19 deletions(-) delete mode 100644 packages/fluent-theme/src/overrides/TextArea.module.css diff --git a/packages/fluent-theme/src/overrides/CodeBlockContent.module.css b/packages/fluent-theme/src/overrides/CodeBlockContent.module.css index 43275aacc4..14bb71411d 100644 --- a/packages/fluent-theme/src/overrides/CodeBlockContent.module.css +++ b/packages/fluent-theme/src/overrides/CodeBlockContent.module.css @@ -17,6 +17,7 @@ } } } +/* Scrollbars */ :global(.webchat-fluent).theme .code-block-content .code-block-content__code-block, :global(.webchat-fluent).theme .send-box-attachment-bar.send-box-attachment-bar--as-list-item, :global(.webchat-fluent).theme .text-area.text-area--scroll, @@ -50,6 +51,15 @@ background-color: transparent; } + &:hover::-webkit-scrollbar-thumb, + &:has(> .text-area-input:focus)::-webkit-scrollbar-thumb { + background-clip: padding-box; + background-color: var(--webchat-colorNeutralForeground5); + border-radius: 10px; + border-right: 4px solid transparent; + border: 3px solid transparent; + } + &:hover::-webkit-scrollbar-thumb:hover { background-clip: padding-box; background-color: var(--webchat-colorNeutralForeground5); diff --git a/packages/fluent-theme/src/overrides/TextArea.module.css b/packages/fluent-theme/src/overrides/TextArea.module.css deleted file mode 100644 index 3d6c405296..0000000000 --- a/packages/fluent-theme/src/overrides/TextArea.module.css +++ /dev/null @@ -1,17 +0,0 @@ -/* Scrollbars */ -:global(.webchat-fluent).theme .code-block-content .code-block-content__code-block, -:global(.webchat-fluent).theme .send-box-attachment-bar.send-box-attachment-bar--as-list-item, -:global(.webchat-fluent).theme .text-area.text-area--scroll, -:global(.webchat-fluent).theme :global(.webchat__basic-transcript .webchat__basic-transcript__scrollable), -:global(.webchat-fluent).theme .render-markdown [data-math-type='block'], -:global(.webchat-fluent).theme .view-code-dialog__body, -:global(.webchat-fluent).theme :global(.view-code-dialog__code-body) { - &:hover::-webkit-scrollbar-thumb, - &:has(> .text-area-input:focus)::-webkit-scrollbar-thumb { - background-clip: padding-box; - background-color: var(--webchat-colorNeutralForeground5); - border-radius: 10px; - border-right: 4px solid transparent; - border: 3px solid transparent; - } -} diff --git a/packages/fluent-theme/src/private/fluentCSSModuleTheme.ts b/packages/fluent-theme/src/private/fluentCSSModuleTheme.ts index 0ccd80a33b..923590c9b8 100644 --- a/packages/fluent-theme/src/private/fluentCSSModuleTheme.ts +++ b/packages/fluent-theme/src/private/fluentCSSModuleTheme.ts @@ -21,7 +21,6 @@ import activityStatusOverrideStyles from '../overrides/ActivityStatus.module.css import attachmentBarOverrideStyles from '../overrides/AttachmentBar.module.css'; import attachmentBarItemOverrideStyles from '../overrides/AttachmentBarItem.module.css'; import filePreviewOverrideStyles from '../overrides/FilePreview.module.css'; -import textAreaOverrideStyles from '../overrides/TextArea.module.css'; import codeBlockOverrideStyles from '../overrides/CodeBlock.module.css'; import viewCodeDialogOverrideStyles from '../overrides/ViewCodeDialog.module.css'; import animatedImageContentOverrideStyles from '../overrides/AnimatedImageContent.module.css'; @@ -53,7 +52,6 @@ const fluentCSSModuleTheme = combineCSSModules( attachmentBarOverrideStyles, attachmentBarItemOverrideStyles, filePreviewOverrideStyles, - textAreaOverrideStyles, codeBlockOverrideStyles, viewCodeDialogOverrideStyles, animatedImageContentOverrideStyles, From 4acd8cc7817d08dd899ce5073f2c1ecc7fa299ed Mon Sep 17 00:00:00 2001 From: Eugene Olonov Date: Fri, 30 Oct 2026 21:17:10 -0700 Subject: [PATCH 22/24] Fail the CSS check of the build when --webchat-preserve-reason reaches the output CSS A second rule in the lightning-css vibe-grep preset, which component, bundle and fluent-theme already run on their output CSS after the build. --- packages/vibe-grep/src/rules/lightning-css.yaml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/packages/vibe-grep/src/rules/lightning-css.yaml b/packages/vibe-grep/src/rules/lightning-css.yaml index b398506de3..a163f6b026 100644 --- a/packages/vibe-grep/src/rules/lightning-css.yaml +++ b/packages/vibe-grep/src/rules/lightning-css.yaml @@ -17,3 +17,19 @@ description: "Check if CSS is compiled by Lightning CSS plugin conforms to expec message: "Output CSS does not conform to Lightning CSS selector pattern. Check that the CSS file is compiled correctly." severity: error args: [dist/*.css] + +--- +# This rule checks that the Lightning CSS plugin removed every "--webchat-preserve-reason" declaration. +# The declaration keeps the class name of a rule without styles of its own (e.g. a class for themes) in the class map, +# and esbuildPlugins.mjs removes it once the class map is built, so it must not appear in the output CSS. + +id: no-preserve-reason-lightning-css +language: Css +rule: + kind: property_name + regex: "^--webchat-preserve-reason$" + +description: "Check that CSS compiled by Lightning CSS plugin has no --webchat-preserve-reason declarations" +message: "Output CSS has a --webchat-preserve-reason declaration. Check that esbuildPlugins.mjs removes it after the class map is built." +severity: error +args: [dist/*.css] From fa746369a19bac3cbc248afc77cd3f3222e99378 Mon Sep 17 00:00:00 2001 From: Eugene Olonov Date: Fri, 30 Oct 2026 21:35:20 -0700 Subject: [PATCH 23/24] Run the second Lightning CSS pass only on the CSS of CSS modules, not on their class maps The class map of a CSS module also goes through this onLoad. It has no --webchat-preserve-reason today, but would with dashed ident renaming, and Lightning CSS cannot parse JavaScript. --- esbuildPlugins.mjs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/esbuildPlugins.mjs b/esbuildPlugins.mjs index 2f735547ed..f5505369b3 100644 --- a/esbuildPlugins.mjs +++ b/esbuildPlugins.mjs @@ -42,7 +42,10 @@ const cssPlugin = { build.onLoad({ filter: /(\.module_built\.css|\?css_module)$/ }, async args => { const result = await fn(args); - return typeof result?.contents === 'string' && result.contents.includes(PRESERVE_REASON_PROPERTY) + // Only the CSS of a CSS module, not its JavaScript class map, and only if it has the declaration. + return args.path.endsWith('.module_built.css') && + typeof result?.contents === 'string' && + result.contents.includes(PRESERVE_REASON_PROPERTY) ? { ...result, contents: transform({ From 1255369bbe3b54f3a83cf3de24c841014f1eb58a Mon Sep 17 00:00:00 2001 From: Eugene Olonov Date: Fri, 30 Oct 2026 23:30:26 -0700 Subject: [PATCH 24/24] Put every Fluent rule for Web Chat elements in one override CSS module Replaces the 26 override modules, one per Web Chat CSS module, with fluentCSSModuleTheme.module.css, which keeps the rules in their original order and regions, scoped by :global(.webchat-fluent) as before. Fluent's elements now get one class for each scope name (e.g. ".theme") from it, instead of one per override module. --- .../src/overrides/ActivityButton.module.css | 75 - .../src/overrides/ActivityStatus.module.css | 7 - .../overrides/AdaptiveCardRenderer.module.css | 71 - .../overrides/AnimatedImageContent.module.css | 8 - .../src/overrides/AttachmentBar.module.css | 13 - .../overrides/AttachmentBarItem.module.css | 44 - .../src/overrides/BorderFlair.module.css | 16 - .../src/overrides/Bubble.module.css | 153 -- .../overrides/CarouselFilmStrip.module.css | 19 - .../src/overrides/CodeBlock.module.css | 45 - .../src/overrides/CodeBlockContent.module.css | 70 - .../overrides/CodeBlockCopyButton.module.css | 65 - .../overrides/CollapsibleContent.module.css | 22 - .../overrides/CollapsibleGrouping.module.css | 156 -- .../src/overrides/ComponentIcon.module.css | 18 - .../src/overrides/FeedbackForm.module.css | 97 -- .../src/overrides/FilePreview.module.css | 5 - .../src/overrides/LinkDefinitions.module.css | 127 -- .../overrides/MessageStatusLoader.module.css | 13 - .../overrides/PartGroupingActivity.module.css | 6 - .../src/overrides/RenderMarkdown.module.css | 97 -- .../src/overrides/StackedLayout.module.css | 176 -- .../src/overrides/TextContent.module.css | 53 - .../src/overrides/ThumbButton.module.css | 15 - .../src/overrides/TranscriptFocus.module.css | 68 - .../src/overrides/ViewCodeDialog.module.css | 64 - .../private/fluentCSSModuleTheme.module.css | 1456 +++++++++++++++++ .../src/private/fluentCSSModuleTheme.spec.ts | 102 +- .../src/private/fluentCSSModuleTheme.ts | 61 +- 29 files changed, 1524 insertions(+), 1598 deletions(-) delete mode 100644 packages/fluent-theme/src/overrides/ActivityButton.module.css delete mode 100644 packages/fluent-theme/src/overrides/ActivityStatus.module.css delete mode 100644 packages/fluent-theme/src/overrides/AdaptiveCardRenderer.module.css delete mode 100644 packages/fluent-theme/src/overrides/AnimatedImageContent.module.css delete mode 100644 packages/fluent-theme/src/overrides/AttachmentBar.module.css delete mode 100644 packages/fluent-theme/src/overrides/AttachmentBarItem.module.css delete mode 100644 packages/fluent-theme/src/overrides/BorderFlair.module.css delete mode 100644 packages/fluent-theme/src/overrides/Bubble.module.css delete mode 100644 packages/fluent-theme/src/overrides/CarouselFilmStrip.module.css delete mode 100644 packages/fluent-theme/src/overrides/CodeBlock.module.css delete mode 100644 packages/fluent-theme/src/overrides/CodeBlockContent.module.css delete mode 100644 packages/fluent-theme/src/overrides/CodeBlockCopyButton.module.css delete mode 100644 packages/fluent-theme/src/overrides/CollapsibleContent.module.css delete mode 100644 packages/fluent-theme/src/overrides/CollapsibleGrouping.module.css delete mode 100644 packages/fluent-theme/src/overrides/ComponentIcon.module.css delete mode 100644 packages/fluent-theme/src/overrides/FeedbackForm.module.css delete mode 100644 packages/fluent-theme/src/overrides/FilePreview.module.css delete mode 100644 packages/fluent-theme/src/overrides/LinkDefinitions.module.css delete mode 100644 packages/fluent-theme/src/overrides/MessageStatusLoader.module.css delete mode 100644 packages/fluent-theme/src/overrides/PartGroupingActivity.module.css delete mode 100644 packages/fluent-theme/src/overrides/RenderMarkdown.module.css delete mode 100644 packages/fluent-theme/src/overrides/StackedLayout.module.css delete mode 100644 packages/fluent-theme/src/overrides/TextContent.module.css delete mode 100644 packages/fluent-theme/src/overrides/ThumbButton.module.css delete mode 100644 packages/fluent-theme/src/overrides/TranscriptFocus.module.css delete mode 100644 packages/fluent-theme/src/overrides/ViewCodeDialog.module.css create mode 100644 packages/fluent-theme/src/private/fluentCSSModuleTheme.module.css diff --git a/packages/fluent-theme/src/overrides/ActivityButton.module.css b/packages/fluent-theme/src/overrides/ActivityButton.module.css deleted file mode 100644 index 2bfd5abdb9..0000000000 --- a/packages/fluent-theme/src/overrides/ActivityButton.module.css +++ /dev/null @@ -1,75 +0,0 @@ -:global(.webchat-fluent) .part-grouping-decorator { - .stacked-layout .collapsible-grouping__header { - .activity-button { - background: transparent; - border: none; - cursor: pointer; - font-size: var(--webchat-fontSizeBase300); - padding: var(--webchat-spacingVerticalXXS) var(--webchat-spacingHorizontalXS); - - &::after { - content: ''; - cursor: pointer; - display: block; - inset: 0; - position: absolute; - } - } - - .activity-button__text { - font-size: 0; - height: 0; - overflow: clip; - position: absolute; - width: 0; - } - } -} -/* Code block */ - -/* View Code dialog */ - -/* Activity button */ -:global(.webchat-fluent).theme .activity-button { - background: var(--webchat-colorNeutralBackground1); - border-radius: var(--webchat-borderRadiusMedium); - border: var(--webchat-strokeWidthThin) solid var(--webchat-colorNeutralStroke1); - color: var(--webchat-colorNeutralForeground1); - gap: var(--webchat-spacingHorizontalXS); - padding: 5px var(--webchat-spacingHorizontalM); - - &:hover { - background: var(--webchat-colorNeutralBackground1Hover); - border: var(--webchat-strokeWidthThin) solid var(--webchat-colorNeutralStroke1Hover); - color: var(--webchat-colorNeutralForeground1Hover); - } - - &:active { - background: var(--webchat-colorNeutralBackground1Pressed); - border: var(--webchat-strokeWidthThin) solid var(--webchat-colorNeutralStroke1Pressed); - color: var(--webchat-colorNeutralForeground1Pressed); - } - - &:focus-visible { - background: var(--webchat-colorNeutralBackground1); - outline: var(--webchat-strokeWidthThick) solid var(--webchat-colorStrokeFocus2); - outline-offset: calc(var(--webchat-strokeWidthThick) * -1); - } - - &[aria-disabled='true'] { - background: var(--webchat-colorNeutralBackgroundDisabled); - border: var(--webchat-strokeWidthThin) solid var(--webchat-colorNeutralStrokeDisabled); - color: var(--webchat-colorNeutralForegroundDisabled); - } - - .activity-button__icon { - height: 20px; - width: 20px; - - --webchat__component-icon--color: var(--webchat-colorNeutralForeground2); - - @media (forced-colors: active) { - --webchat__component-icon--color: ButtonText; - } - } -} diff --git a/packages/fluent-theme/src/overrides/ActivityStatus.module.css b/packages/fluent-theme/src/overrides/ActivityStatus.module.css deleted file mode 100644 index a0c463d4bf..0000000000 --- a/packages/fluent-theme/src/overrides/ActivityStatus.module.css +++ /dev/null @@ -1,7 +0,0 @@ -:global(.webchat-fluent) .part-grouping-decorator { - &.variant-copilot { - .activity-status { - margin: 0 0 var(--webchat-spacingHorizontalXXS); - } - } -} diff --git a/packages/fluent-theme/src/overrides/AdaptiveCardRenderer.module.css b/packages/fluent-theme/src/overrides/AdaptiveCardRenderer.module.css deleted file mode 100644 index cc8043e7b4..0000000000 --- a/packages/fluent-theme/src/overrides/AdaptiveCardRenderer.module.css +++ /dev/null @@ -1,71 +0,0 @@ -:global(.webchat-fluent) .part-grouping-decorator { - &.variant-copilot { - &.part-grouping-decorator--from-bot { - .bubble { - .bubble__content .adaptive-card-renderer > :global(.ac-adaptiveCard) { - font-family: var(--webchat-fontFamilyBase); - /* Remove double padding for adaptive cards inside bubble */ - padding: unset !important; - - :global(.ac-textBlock) { - font-family: var(--webchat-fontFamilyBase) !important; - } - - :global(.ac-container:not([style*='background-color:']) .ac-textBlock), - > :global(.ac-textBlock) { - color: currentColor !important; - } - - :global(.ac-pushButton) { - background-color: var(--webchat-colorNeutralBackground1); - color: var(--webchat-colorNeutralForeground1); - border: var(--webchat-strokeWidthThin) solid var(--webchat-colorNeutralStroke1); - border-radius: var(--webchat-borderRadiusLarge); - transition: all var(--webchat-durationNormal) var(--webchat-curveDecelerateMid); - } - - :global(.ac-pushButton:hover) { - background-color: var(--webchat-colorNeutralBackground1Hover); - border-color: var(--webchat-colorNeutralStroke1Hover); - color: var(--webchat-colorNeutralForeground1Hover); - cursor: pointer; - } - - :global(.ac-pushButton:active), - :global(.ac-pushButton).adaptive-card-renderer__action--selected { - background-color: var(--webchat-colorNeutralBackground1Pressed); - border-color: var(--webchat-colorNeutralStroke1Pressed); - color: var(--webchat-colorNeutralForeground1Pressed); - outline-style: none; - } - - :global(.ac-pushButton:focus-visible) { - border-color: var(--webchat-colorStrokeFocus2); - border-width: var(--webchat-strokeWidthThin); - outline: var(--webchat-strokeWidthThick) solid var(--webchat-colorTransparentStroke); - box-shadow: 0 0 0 var(--webchat-strokeWidthThin) var(--webchat-colorStrokeFocus2) inset; - } - - :global(.ac-pushButton.primary) { - background-color: var(--webchat-colorBrandBackground); - border-color: transparent; - color: var(--webchat-colorNeutralForegroundOnBrand); - } - - :global(.ac-pushButton.primary:hover) { - background-color: var(--webchat-colorBrandBackgroundHover); - border-color: transparent; - color: var(--webchat-colorNeutralForegroundOnBrand); - } - - :global(.ac-pushButton.primary:active), - :global(.ac-pushButton):global(.primary).adaptive-card-renderer__action--selected { - background-color: var(--webchat-colorBrandBackgroundPressed); - border-color: transparent; - color: var(--webchat-colorNeutralForegroundOnBrand); - } - } - } - } - } -} diff --git a/packages/fluent-theme/src/overrides/AnimatedImageContent.module.css b/packages/fluent-theme/src/overrides/AnimatedImageContent.module.css deleted file mode 100644 index c629f32e57..0000000000 --- a/packages/fluent-theme/src/overrides/AnimatedImageContent.module.css +++ /dev/null @@ -1,8 +0,0 @@ -/* Pause button over animated images: a band in the button background around the focus indicator keeps it visible over any image */ -:global(.webchat-fluent).theme .activity-button.animated-image-content__button:focus-visible::after { - outline: var(--webchat-strokeWidthThick) solid var(--webchat-colorNeutralBackground1); - - @media (forced-colors: active) { - outline-color: ButtonFace; - } -} diff --git a/packages/fluent-theme/src/overrides/AttachmentBar.module.css b/packages/fluent-theme/src/overrides/AttachmentBar.module.css deleted file mode 100644 index 8023ff3df7..0000000000 --- a/packages/fluent-theme/src/overrides/AttachmentBar.module.css +++ /dev/null @@ -1,13 +0,0 @@ -:global(.webchat-fluent) .sendbox__attachment-bar.send-box-attachment-bar { - .send-box-attachment-bar__box { - gap: var(--webchat-spacingVerticalXS) var(--webchat-spacingHorizontalXS); - } - - &.send-box-attachment-bar--as-list-item { - margin-inline-end: calc(-1 * var(--webchat-spacingHorizontalS)); - } - - &.send-box-attachment-bar--as-list-item .send-box-attachment-bar__box:not(:empty) { - padding: var(--webchat-spacingVerticalXS) var(--webchat-spacingHorizontalXS); - } -} diff --git a/packages/fluent-theme/src/overrides/AttachmentBarItem.module.css b/packages/fluent-theme/src/overrides/AttachmentBarItem.module.css deleted file mode 100644 index 70303fbccc..0000000000 --- a/packages/fluent-theme/src/overrides/AttachmentBarItem.module.css +++ /dev/null @@ -1,44 +0,0 @@ -:global(.webchat-fluent) .sendbox__attachment-bar.send-box-attachment-bar { - .send-box-attachment-bar-item.send-box-attachment-bar-item--as-list-item { - box-shadow: var(--webchat-shadow2); - border-radius: var(--webchat-borderRadiusMedium); - padding: var(--webchat-spacingVerticalXXS) var(--webchat-spacingHorizontalXXS); - } - - .send-box-attachment-bar-item.send-box-attachment-bar-item--as-thumbnail { - border: var(--webchat-strokeWidthThin) solid var(--webchat-colorNeutralStroke1Selected); - border-radius: var(--webchat-borderRadiusXLarge); - } - - .send-box-attachment-bar-item .send-box-attachment-bar-item__delete-button { - background-color: var(--webchat-colorNeutralBackground1); - border-radius: var(--webchat-borderRadiusMedium); - color: var(--webchat-colorNeutralForeground1); - transition: - background-color var(--webchat-durationNormal) var(--webchat-curveAccelerateMid), - color var(--webchat-durationNormal) var(--webchat-curveAccelerateMid), - opacity var(--webchat-durationUltraFast) var(--webchat-curveAccelerateMid); - - &:hover { - background-color: var(--webchat-colorNeutralBackground1Hover); - color: var(--webchat-colorNeutralForeground1Hover); - } - - &:active { - background-color: var(--webchat-colorNeutralBackground1Pressed); - color: var(--webchat-colorNeutralForeground1Pressed); - } - - &:disabled, - &[aria-disabled='true'] { - background-color: var(--webchat-colorNeutralBackgroundDisabled); - border-color: var(--webchat-colorNeutralStrokeDisabled); - color: var(--webchat-colorNeutralForegroundDisabled); - } - } - - .send-box-attachment-bar-item.send-box-attachment-bar-item.send-box-attachment-bar-item.send-box-attachment-bar-item--as-list-item - .send-box-attachment-bar-item__preview { - padding-inline: var(--webchat-spacingHorizontalS); - } -} diff --git a/packages/fluent-theme/src/overrides/BorderFlair.module.css b/packages/fluent-theme/src/overrides/BorderFlair.module.css deleted file mode 100644 index 4e9eb18ad6..0000000000 --- a/packages/fluent-theme/src/overrides/BorderFlair.module.css +++ /dev/null @@ -1,16 +0,0 @@ -:global(.webchat-fluent) .part-grouping-decorator { - &.variant-fluent { - &.part-grouping-decorator--group { - .border-flair { - border-radius: var(--webchat-part-grouping__bubble--border-radius); - } - } - } - &.variant-copilot { - .border-flair { - border-radius: var(--webchat-borderRadius2XLarge); - inset: anchor(top) anchor(right) anchor(bottom) anchor(left); - position-anchor: --webchat-flair; - } - } -} diff --git a/packages/fluent-theme/src/overrides/Bubble.module.css b/packages/fluent-theme/src/overrides/Bubble.module.css deleted file mode 100644 index 7448388bfc..0000000000 --- a/packages/fluent-theme/src/overrides/Bubble.module.css +++ /dev/null @@ -1,153 +0,0 @@ -:global(.webchat-fluent) .part-grouping-decorator { - .stacked-layout .bubble :global(.webchat__fileContent__badge) { - cursor: default; - font-size: var(--webchat-fontSizeBase300); - line-height: var(--webchat-lineHeightBase300); - - :global(.webchat__fileContent__fileName) { - color: var(--webchat-colorBrandForegroundLink); - font-family: inherit; - } - - :global(.webchat__fileContent__size) { - color: var(--webchat-colorNeutralForeground2); - font-family: inherit; - } - } - - .stacked-layout .bubble :global(.webchat__fileContent__downloadIcon) { - color: var(--webchat-colorBrandForegroundLink); - } - - .collapsible-grouping { - :global(.part-grouping-decorator) .bubble .bubble__content { - box-sizing: content-box; - gap: var(--webchat-spacingVerticalXS); - } - } - - .stacked-layout .stacked-layout__content:has(.bubble) { - max-width: 100%; - overflow: visible; - } - .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; - } - - .stacked-layout .bubble:has(:global(.activity-loader)) .bubble__content { - background: unset; - box-shadow: unset; - border-color: transparent; - } - - .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); - border-width: 0; - box-shadow: var(--webchat-part-grouping__bubble-content--shadow); - box-sizing: border-box; - color: var(--webchat-colorNeutralForeground1); - max-width: 100%; - min-height: var(--webchat-part-grouping__bubble--min-height); - } - - /* Forced colors remove the bubble shadow and fill, so draw a border as the default theme does. */ - @media (forced-colors: active) { - &:not(.part-grouping-decorator--group) - .stacked-layout - .bubble:not(:has(:global(.activity-loader))) - .bubble__content { - border: var(--webchat-strokeWidthThin) solid CanvasText; - } - } - &.variant-fluent { - &.part-grouping-decorator--group { - .bubble { - position: static; - } - - .bubble .bubble__content { - margin-block: calc(var(--webchat-spacingVerticalS) * -1); - padding-block: var(--webchat-spacingVerticalS); - } - } - } - &.variant-copilot { - &.part-grouping-decorator--from-bot { - .bubble { - position: static; - width: var(--webchat-part-grouping__bubble--max-width); - } - - &:not(.part-grouping-decorator--group) { - .bubble__content { - display: flex; - flex-direction: column; - gap: var(--webchat-spacingVerticalS); - } - } - } - - &.part-grouping-decorator--group { - .bubble .bubble__content { - margin-block: calc(var(--webchat-spacingVerticalS) * -1); - padding-block: var(--webchat-spacingVerticalS); - } - } - - &.part-grouping-decorator--from-bot:not(.part-grouping-decorator--group) .bubble__content { - box-sizing: content-box; - padding-block: var(--webchat-spacingVerticalM); - padding-inline: var(--webchat-spacingHorizontalL); - } - - &:not(.part-grouping-decorator--group) .stacked-layout .bubble .bubble__content { - border-width: var(--webchat-strokeWidthThin); - border-style: solid; - } - - &:not(.part-grouping-decorator--group) .bubble:not(.bubble--from-user) .bubble__content { - anchor-name: --webchat-flair; - max-width: unset; - } - - &:has(.bubble--from-user) { - .bubble { - margin-block-end: var(--webchat-spacingVerticalM); - } - } - - &:has(.bubble.bubble--from-user), - &:has(:global(.pre-chat-message-activity)), - &:has(:global(.liner-message-activity)) { - padding-inline-start: var(--webchat-spacingHorizontalNone); - } - } -} -:global(.webchat-fluent).theme .transcript-focus-area:focus-visible .transcript-focus-area__content--focused { - /* Transcript activity focused directly */ - > :global(.webchat__focus-trap) > :global(.webchat__basic-transcript__activity-body) .bubble { - display: grid; - grid-template-areas: 'focused-content'; - position: static; - - .bubble__content { - grid-area: focused-content; - } - - &::after { - border-radius: var(--webchat-part-grouping__bubble--border-radius); - content: ''; - grid-area: focused-content; - outline: var(--webchat-strokeWidthThick) solid var(--webchat-colorStrokeFocus2); - pointer-events: none; - } - } - - :global(.activity-decorator) .bubble .bubble__nub-pad { - display: none; - } -} diff --git a/packages/fluent-theme/src/overrides/CarouselFilmStrip.module.css b/packages/fluent-theme/src/overrides/CarouselFilmStrip.module.css deleted file mode 100644 index 67dc41febb..0000000000 --- a/packages/fluent-theme/src/overrides/CarouselFilmStrip.module.css +++ /dev/null @@ -1,19 +0,0 @@ -:global(.webchat-fluent) .part-grouping-decorator { - .carousel-layout { - .carousel-filmstrip__avatar-gutter, - .carousel-filmstrip__alignment-pad { - margin: 0; - width: var(--webchat-spacingHorizontalMNudge); - } - } - &.variant-copilot { - &.part-grouping-decorator--from-bot { - .carousel-layout { - .carousel-filmstrip__avatar-gutter, - .carousel-filmstrip__alignment-pad { - width: 0; - } - } - } - } -} diff --git a/packages/fluent-theme/src/overrides/CodeBlock.module.css b/packages/fluent-theme/src/overrides/CodeBlock.module.css deleted file mode 100644 index 160e3a8997..0000000000 --- a/packages/fluent-theme/src/overrides/CodeBlock.module.css +++ /dev/null @@ -1,45 +0,0 @@ -:global(.webchat-fluent).theme .code-block { - border: none; - font-family: var(--webchat-fontFamilyMonospace); - font-size: var(--webchat-fontSizeBase300); - - --webchat__code-block__copy-button--color: var(--webchat-colorNeutralForeground1); - --webchat__code-block__copy-button--background: var(--webchat-colorNeutralBackground3); - - &:has(.code-block__body:focus-visible):focus-within { - outline: var(--webchat-strokeWidthThick) solid var(--webchat-colorStrokeFocus2); - outline-offset: calc(var(--webchat-strokeWidthThick) * -1); - - .code-block__body { - outline: none; - } - } - - .code-block__body { - /* TODO: what is the right value? The value below matches "normal" for contents display */ - line-height: 1.435; - } - - &:has(> .code-block__theme--github-dark-default) { - --webchat__background--code-block: var(--codeBlockBackground, var(--webchat-colorGrey8)); - --webchat__code-block__copy-button--background: var( - --codeBlockCopyButtonBackgroundPressed, - var(--webchat-colorGrey14) - ); - --webchat__code-block__copy-button--color: var(--codeBlockCopyButtonForeground, var(--webchat__color--code-block)); - --webchat__color--code-block: var(--codeBlockForeground, var(--webchat-colorGrey98)); - } - - &:has(> .code-block__theme--github-light-default) { - --webchat__background--code-block: var(--codeBlockBackground, var(--webchat-colorGrey98)); - --webchat__code-block__copy-button--background: var(--codeBlockBackgroundPressed, var(--webchat-colorGrey92)); - --webchat__code-block__copy-button--color: var(--codeBlockCopyButtonForeground, var(--webchat__color--code-block)); - --webchat__color--code-block: var(--codeBlockForeground, var(--webchat-colorGrey8)); - } -} -:global(.webchat-fluent).theme :global(.webchat__modal-dialog).view-code-dialog { - .code-block__body { - /* TODO: what is the right value? The value below matches "normal" for block display */ - line-height: normal; - } -} diff --git a/packages/fluent-theme/src/overrides/CodeBlockContent.module.css b/packages/fluent-theme/src/overrides/CodeBlockContent.module.css deleted file mode 100644 index 14bb71411d..0000000000 --- a/packages/fluent-theme/src/overrides/CodeBlockContent.module.css +++ /dev/null @@ -1,70 +0,0 @@ -:global(.webchat-fluent) .part-grouping-decorator { - .stacked-layout .bubble .code-block-content { - border-radius: var(--webchat-borderRadiusXLarge); - border: var(--webchat-strokeWidthThin) solid var(--webchat-colorNeutralStroke1); - font-size: var(--webchat-fontSizeBase300); - font-weight: var(--webchat-fontWeightRegular); - margin-block: var(--webchat-spacingVerticalNone); - margin-inline: var(--webchat-part-grouping__code-block--space-inline); - - .code-block-content__header { - padding: var(--webchat-spacingVerticalM) var(--webchat-spacingHorizontalL); - } - - .code-block-content__code-block { - padding-block: var(--webchat-spacingVerticalM); - padding-inline: var(--webchat-spacingHorizontalL) var(--webchat-spacingHorizontalS); - } - } -} -/* Scrollbars */ -:global(.webchat-fluent).theme .code-block-content .code-block-content__code-block, -:global(.webchat-fluent).theme .send-box-attachment-bar.send-box-attachment-bar--as-list-item, -:global(.webchat-fluent).theme .text-area.text-area--scroll, -:global(.webchat-fluent).theme :global(.webchat__basic-transcript .webchat__basic-transcript__scrollable), -:global(.webchat-fluent).theme .render-markdown [data-math-type='block'], -:global(.webchat-fluent).theme .view-code-dialog__body, -:global(.webchat-fluent).theme :global(.view-code-dialog__code-body) { - /* Edge uses -webkit-scrollbar if scrollbar-* is not set */ - scrollbar-color: unset; - scrollbar-width: unset; - /* Firefox */ - -moz-scrollbar-color: var(--webchat-colorNeutralForeground5) transparent; - -moz-scrollbar-width: thin; - - &::-webkit-scrollbar { - height: var(--webchat-spacingVerticalMNudge); - width: var(--webchat-spacingVerticalMNudge); - visibility: hidden; - } - - &::-webkit-scrollbar-corner { - background: transparent; - } - - &:hover::-webkit-scrollbar { - height: var(--webchat-spacingVerticalMNudge); - width: var(--webchat-spacingVerticalMNudge); - } - - &::-webkit-scrollbar-thumb { - background-color: transparent; - } - - &:hover::-webkit-scrollbar-thumb, - &:has(> .text-area-input:focus)::-webkit-scrollbar-thumb { - background-clip: padding-box; - background-color: var(--webchat-colorNeutralForeground5); - border-radius: 10px; - border-right: 4px solid transparent; - border: 3px solid transparent; - } - - &:hover::-webkit-scrollbar-thumb:hover { - background-clip: padding-box; - background-color: var(--webchat-colorNeutralForeground5); - border-radius: 10px; - border-right: 2px solid transparent; - border: 1px solid transparent; - } -} diff --git a/packages/fluent-theme/src/overrides/CodeBlockCopyButton.module.css b/packages/fluent-theme/src/overrides/CodeBlockCopyButton.module.css deleted file mode 100644 index 1845975973..0000000000 --- a/packages/fluent-theme/src/overrides/CodeBlockCopyButton.module.css +++ /dev/null @@ -1,65 +0,0 @@ -:global(.webchat-fluent) .part-grouping-decorator { - .stacked-layout .bubble .code-block-content { - .code-block-copy-button { - --webchat__code-block__copy-button--color: var(--webchat-colorNeutralForeground1); - --webchat__code-block__copy-button--background: var(--webchat-colorNeutralBackground3); - - margin-block-start: var(--webchat-spacingVerticalM); - margin-inline-end: var(--webchat-spacingHorizontalL); - position: absolute; - right: 0; - top: 0; - } - } -} -:global(.webchat-fluent).theme .code-block { - .code-block-copy-button { - background: transparent; - border: none; - color: var(--webchat__code-block__copy-button--color); - height: 20px; - margin-inline-start: var(--webchat-spacingHorizontalS); - padding: 0; - width: 20px; - transition: background-color var(--webchat-durationNormal) var(--webchat-curveDecelerateMid); - - /* Without a border, the button has no visible boundary in forced colors, so draw one as the default theme does. */ - @media (forced-colors: active) { - border: var(--webchat-strokeWidthThin) solid ButtonText; - } - - .code-block-copy-button__icon { - --webchat__component-icon--color: currentColor; - - @media (forced-colors: active) { - --webchat__component-icon--color: ButtonText; - } - } - - &:hover { - background: var(--webchat__code-block__copy-button--background); - color: var(--webchat__code-block__copy-button--color); - } - - &:active { - background: var(--webchat__code-block__copy-button--background); - color: var(--webchat__code-block__copy-button--color); - } - - &:focus { - color: var(--webchat__code-block__copy-button--color); - outline-color: currentColor; - } - } -} -:global(.webchat-fluent).theme :global(.webchat__modal-dialog).view-code-dialog { - .code-block-copy-button { - --webchat__code-block__copy-button--color: var(--webchat-colorNeutralForeground1); - --webchat__code-block__copy-button--background: var(--webchat-colorNeutralBackground3); - - margin-block-start: var(--webchat-spacingVerticalS); - position: absolute; - right: 32px; - top: 0; - } -} diff --git a/packages/fluent-theme/src/overrides/CollapsibleContent.module.css b/packages/fluent-theme/src/overrides/CollapsibleContent.module.css deleted file mode 100644 index dd2447455e..0000000000 --- a/packages/fluent-theme/src/overrides/CollapsibleContent.module.css +++ /dev/null @@ -1,22 +0,0 @@ -:global(.webchat-fluent) .part-grouping-decorator { - .stacked-layout .bubble .collapsible-content { - .collapsible-content__summary { - &:focus-visible { - border-radius: var(--webchat-borderRadiusSmall); - outline-offset: 4px; - outline: var(--webchat-strokeWidthThick) solid var(--webchat-colorStrokeFocus2); - } - } - - .collapsible-content__content { - margin-block: var(--webchat-part-grouping__collapsible-content--space-block-start-end); - } - } - &.variant-copilot { - &.part-grouping-decorator--from-bot { - .bubble .collapsible-content .collapsible-content__content { - margin-block: var(--webchat-spacingVerticalNone); - } - } - } -} diff --git a/packages/fluent-theme/src/overrides/CollapsibleGrouping.module.css b/packages/fluent-theme/src/overrides/CollapsibleGrouping.module.css deleted file mode 100644 index edfc210345..0000000000 --- a/packages/fluent-theme/src/overrides/CollapsibleGrouping.module.css +++ /dev/null @@ -1,156 +0,0 @@ -:global(.webchat-fluent) .part-grouping-decorator { - .stacked-layout .collapsible-grouping__header { - justify-content: flex-start; - min-width: 0; - padding: var(--webchat-spacingVerticalNone) var(--webchat-spacingHorizontalNone); - place-self: start; - position: relative; - } - - .stacked-layout .collapsible-grouping { - display: grid; - - &.collapsible-grouping--open { - gap: var(--webchat-spacingVerticalM); - } - } - - .collapsible-grouping, - .collapsible-grouping__header { - border: none; - } - - .collapsible-grouping { - max-width: 100%; - } - &.variant-fluent { - &.part-grouping-decorator--group { - .collapsible-grouping { - anchor-name: --webchat-flair; - border-radius: var(--webchat-part-grouping__bubble--border-radius); - max-width: min(var(--webchat-part-grouping__bubble--max-width), 100%); - padding: var(--webchat-part-grouping__bubble--space-block) var(--webchat-part-grouping__bubble--space-inline); - position: relative; - transition: - background-color var(--webchat-durationNormal) var(--webchat-curveDecelerateMid), - box-shadow var(--webchat-durationNormal) var(--webchat-curveDecelerateMid); - } - - &:has(.collapsible-grouping--open) { - .collapsible-grouping { - background-color: var(--webchat-part-grouping__bubble--background-color); - box-shadow: var(--webchat-part-grouping__bubble--box-shadow); - } - } - } - } - &.variant-copilot { - &.part-grouping-decorator--group { - .collapsible-grouping__content { - border: var(--webchat-strokeWidthThin) solid transparent; - border-radius: var(--webchat-part-grouping__bubble--border-radius); - position: relative; - - @supports (anchor-name: --webchat-flair) { - anchor-name: --webchat-flair; - position: static; - } - } - - &:has(.collapsible-grouping--open) { - .collapsible-grouping__content { - background-color: var(--webchat-part-grouping__bubble--background-color); - border-color: var(--webchat-part-grouping__bubble--border-color); - } - } - } - - .stacked-layout:has(.collapsible-grouping) { - margin-inline: var(--webchat-spacingHorizontalNone); - } - - .collapsible-grouping { - margin-inline: var(--webchat-spacingHorizontalXS); - } - - .collapsible-grouping .collapsible-grouping__chevron, - .collapsible-content .collapsible-content__chevron { - transform: rotate(270deg); - } - - .collapsible-grouping--open .collapsible-grouping__chevron, - .collapsible-content[open] .collapsible-content__chevron { - transform: rotate(360deg); - } - - .collapsible-grouping__header { - margin-inline: calc(var(--webchat-spacingHorizontalXS) * -1); - } - - .collapsible-grouping__title { - color: var(--webchat-colorNeutralForeground2); - font-size: var(--webchat-fontSizeBase200); - font-weight: var(--webchat-fontWeightSemibold); - line-height: var(--webchat-lineHeightBase200); - } - - .collapsible-grouping__content { - anchor-name: --webchat-flair; - background: var(--webchat-colorNeutralBackground1); - background-clip: padding-box; - border-radius: var(--webchat-borderRadius2XLarge); - height: unset; - opacity: unset; - visibility: unset; - } - - .collapsible-grouping__list > * { - interpolate-size: allow-keywords; - transition: - margin calc(var(--webchat__transition-duration) / 2) var(--webchat__transition-easing), - height calc(var(--webchat__transition-duration) / 2) var(--webchat__transition-easing); - } - - .collapsible-grouping__content--open .collapsible-grouping__list > * { - /* We use animation so we can reset needed properties immediately upon switching classes */ - animation: collapsible-grouping__fade-in var(--webchat__transition-duration) - calc(var(--webchat__transition-duration) / 4) var(--webchat__transition-easing) both; - height: auto; - transition: - margin var(--webchat__transition-duration) var(--webchat__transition-easing), - height var(--webchat__transition-duration) var(--webchat__transition-easing); - } - - .collapsible-grouping__content:not(.collapsible-grouping__content--open) { - /* We use animation so we can reset needed properties immediately upon switching classes */ - animation: collapsible-grouping__fade-in var(--webchat__transition-duration) var(--webchat__transition-duration) - var(--webchat__transition-easing) both; - } - - .collapsible-grouping__content:not(.collapsible-grouping__content--open) .collapsible-grouping__list { - padding: var(--webchat-spacingVerticalNone) var(--webchat-spacingHorizontalNone); - } - } -} -/* Transcript focused content */ -:global(.webchat-fluent).theme .transcript-focus-area:focus-visible .transcript-focus-area__content--focused { - .collapsible-grouping::after { - border-radius: var(--webchat-part-grouping__bubble--border-radius); - border: none; - content: ''; - inset: 0; - isolation: isolate; - outline: var(--webchat-strokeWidthThick) solid var(--webchat-colorStrokeFocus2); - pointer-events: none; - position: absolute; - } -} - -/* Transcript focused content copilot variant */ -:global(.webchat-fluent).theme.variant-copilot - .transcript-focus-area:focus-visible - .transcript-focus-area__content--focused { - .collapsible-grouping::after { - inset: -4px; - } -} diff --git a/packages/fluent-theme/src/overrides/ComponentIcon.module.css b/packages/fluent-theme/src/overrides/ComponentIcon.module.css deleted file mode 100644 index 902fb7a11e..0000000000 --- a/packages/fluent-theme/src/overrides/ComponentIcon.module.css +++ /dev/null @@ -1,18 +0,0 @@ -:global(.webchat-fluent) .part-grouping-decorator { - .collapsible-grouping { - .stacked-layout__message-status { - .component-icon { - height: 19px; - width: 19px; - } - } - } - &.variant-copilot { - .stacked-layout .stacked-layout__message-status { - .component-icon { - height: unset; - width: unset; - } - } - } -} diff --git a/packages/fluent-theme/src/overrides/FeedbackForm.module.css b/packages/fluent-theme/src/overrides/FeedbackForm.module.css deleted file mode 100644 index 2523b8fa63..0000000000 --- a/packages/fluent-theme/src/overrides/FeedbackForm.module.css +++ /dev/null @@ -1,97 +0,0 @@ -/* Feedback Form */ -:global(.webchat-fluent).theme .feedback-form { - .feedback-form__form-header { - color: var(--webchat-colorNeutralForeground1); - font-size: var(--webchat-fontSizeBase300); - font-weight: var(--webchat-fontWeightRegular); - line-height: var(--webchat-lineHeightBase300); - } - - .feedback-form__form { - gap: var(--webchat-spacingVerticalXS); - } - - .feedback-form__form-footer { - color: var(--webchat-colorNeutralForeground1); - font-size: var(--webchat-fontSizeBase100); - font-weight: var(--webchat-fontWeightRegular); - line-height: var(--webchat-lineHeightBase200); - } - - .feedback-form__submit-button { - background-color: var(--webchat-colorBrandBackground); - border: var(--webchat-strokeWidthThin) solid var(--webchat-colorBrandBackground); - border-radius: var(--webchat-borderRadiusMedium); - color: var(--webchat-colorNeutralForegroundOnBrand); - cursor: pointer; - font-size: var(--webchat-fontSizeBase300); - padding: var(--webchat-spacingVerticalNone) var(--webchat-spacingHorizontalS); - transition: background-color var(--webchat-durationNormal) var(--webchat-curveDecelerateMid); - - &:hover { - background-color: var(--webchat-colorBrandBackgroundHover); - border: var(--webchat-strokeWidthThin) solid var(--webchat-colorBrandBackgroundHover); - } - - &:focus-visible { - outline: var(--webchat-strokeWidthThick) solid var(--webchat-colorStrokeFocus2); - outline-offset: calc(var(--webchat-strokeWidthThin) * -1); - } - - &:active { - background-color: var(--webchat-colorBrandBackgroundPressed); - border: var(--webchat-strokeWidthThin) solid var(--webchat-colorBrandBackgroundPressed); - } - } - - .feedback-form__cancel-button { - background-color: var(--webchat-colorNeutralBackground1); - border: var(--webchat-strokeWidthThin) solid var(--webchat-colorNeutralStroke1); - border-radius: var(--webchat-borderRadiusMedium); - color: var(--webchat-colorNeutralForeground1); - cursor: pointer; - font-size: var(--webchat-fontSizeBase300); - padding: var(--webchat-spacingVerticalNone) var(--webchat-spacingHorizontalS); - transition: background-color var(--webchat-durationNormal) var(--webchat-curveDecelerateMid); - - &:hover { - background-color: var(--webchat-colorNeutralBackground1Hover); - border: var(--webchat-strokeWidthThin) solid var(--webchat-colorNeutralStroke1Hover); - color: currentColor; - } - - &:focus-visible { - outline: var(--webchat-strokeWidthThick) solid var(--webchat-colorStrokeFocus2); - outline-offset: calc(var(--webchat-strokeWidthThin) * -1); - } - - &:active { - background-color: var(--webchat-colorNeutralBackground1Pressed); - border: var(--webchat-strokeWidthThin) solid var(--webchat-colorNeutralStroke1Pressed); - } - } - - .feedback-form__text-box { - background-color: var(--webchat-colorNeutralBackground1); - border-radius: var(--webchat-borderRadiusMedium); - border: var(--webchat-strokeWidthThin) solid var(--webchat-colorNeutralStroke1); - font-size: var(--webchat-fontSizeBase300); - line-height: var(--webchat-lineHeightBase300); - gap: var(--webchat-spacingVerticalSNudge); - padding: var(--webchat-spacingVerticalS) var(--webchat-spacingHorizontalM); - - &::after { - border-bottom-left-radius: var(--webchat-borderRadiusMedium); - border-bottom-right-radius: var(--webchat-borderRadiusMedium); - border-bottom: var(--webchat-strokeWidthThicker) solid var(--webchat-colorBrandForeground1); - clip-path: inset(calc(100% - var(--webchat-strokeWidthThicker)) 50% 0 50%); - } - &:focus-within { - border: var(--webchat-strokeWidthThin) solid var(--webchat-colorNeutralStroke1); - } - &:focus-within::after { - clip-path: inset(calc(100% - var(--webchat-strokeWidthThicker)) 0 0 0); - transition: clip-path var(--webchat-durationNormal) var(--webchat-curveDecelerateMid); - } - } -} diff --git a/packages/fluent-theme/src/overrides/FilePreview.module.css b/packages/fluent-theme/src/overrides/FilePreview.module.css deleted file mode 100644 index eaef762617..0000000000 --- a/packages/fluent-theme/src/overrides/FilePreview.module.css +++ /dev/null @@ -1,5 +0,0 @@ -:global(.webchat-fluent) .sendbox__attachment-bar.send-box-attachment-bar { - .send-box-attachment-bar-item-file-preview.send-box-attachment-bar-item-file-preview--as-list-item { - gap: var(--webchat-spacingVerticalS) var(--webchat-spacingHorizontalS); - } -} diff --git a/packages/fluent-theme/src/overrides/LinkDefinitions.module.css b/packages/fluent-theme/src/overrides/LinkDefinitions.module.css deleted file mode 100644 index 43619cef10..0000000000 --- a/packages/fluent-theme/src/overrides/LinkDefinitions.module.css +++ /dev/null @@ -1,127 +0,0 @@ -:global(.webchat-fluent) .part-grouping-decorator { - .link-definitions__header { - border-radius: var(--webchat-borderRadiusMedium); - - .link-definitions__header-text { - color: var(--webchat-colorNeutralForeground3); - } - - .link-definitions__message-sensitivity-label-text { - color: var(--webchat-colorNeutralForeground4); - } - - .link-definitions__header-chevron { - color: var(--webchat-colorNeutralForeground3); - font-size: var(--webchat__font-size--small); - } - - &:focus-visible { - outline-offset: 1px; - outline: var(--webchat-strokeWidthThin) solid var(--webchat-colorStrokeFocus2); - } - } - - .link-definitions { - --webchat__citation__external-link--mask: var(--webchat-externalLink-mask); - --webchat__citation__link--max-width: var(--webchat-externalLink-maxWidth); - - .link-definitions__list { - color: var(--webchat__color--subtle); - display: flex; - flex-flow: row wrap; - gap: var(--webchat-spacingHorizontalS); - } - - .link-definitions__list-item { - border-radius: var(--webchat-borderRadiusMedium); - max-width: var(--webchat__citation__link--max-width); - } - - .link-definitions__list-item-box { - background-color: var(--webchat-colorNeutralBackground3); - border-radius: var(--webchat-borderRadiusMedium); - border: var(--webchat-strokeWidthThin) solid var(--webchat-colorNeutralStroke2); - box-sizing: border-box; - color: currentColor; - display: inline-flex; - font-size: var(--webchat__font-size--small); - height: 24px; - padding-inline-end: var(--webchat-spacingHorizontalS); - - /* Button chips use the link color like link chips; the badge, divider and text inherit it. */ - @media (forced-colors: active) { - border-color: LinkText; - color: LinkText; - } - } - - .link-definitions__list-item-body { - font-family: inherit; - gap: var(--webchat-spacingHorizontalSNudge); - min-width: 0; - padding: 0; - } - - .link-definitions__badge { - align-self: center; - background-color: transparent; - border-radius: 0; - border: none; - color: currentColor; - font-size: var(--webchat-fontSizeBase100); - font-weight: var(--webchat-fontWeightSemibold); - line-height: var(--webchat-lineHeightBase100); - margin: 0; - min-width: 20px; - padding: 0; - position: relative; - text-align: center; - } - - .link-definitions__badge::after { - border-right: var(--webchat-strokeWidthThin) solid var(--webchat-colorNeutralStroke2); - bottom: 0; - content: ''; - display: block; - height: 16px; - position: absolute; - right: 0; - top: 0; - width: 0; - } - - .link-definitions__list-item-main-text { - gap: var(--webchat-spacingHorizontalXS); - } - - .link-definitions__list-item-text { - color: currentColor; - text-decoration: none; - text-overflow: ellipsis; - } - - .link-definitions__list-item-main-text:has(.link-definitions__open-in-new-window-icon)::before { - align-self: center; - background: currentColor; - color: currentColor; - content: ''; - flex: none; - font-size: var(--webchat-fontSizeBase400); - height: 1em; - -webkit-mask: var(--webchat__citation__external-link--mask) no-repeat; - mask: var(--webchat__citation__external-link--mask) no-repeat; - padding: 0; - width: 1em; - - @media (forced-colors: active) { - background: LinkText; - } - } - - /* It seems Copilot does not show per-item sensitivity label, i.e. "General", etc. */ - .link-definitions__list-item-badge, - .link-definitions__open-in-new-window-icon { - display: none; - } - } -} diff --git a/packages/fluent-theme/src/overrides/MessageStatusLoader.module.css b/packages/fluent-theme/src/overrides/MessageStatusLoader.module.css deleted file mode 100644 index 74d42566f2..0000000000 --- a/packages/fluent-theme/src/overrides/MessageStatusLoader.module.css +++ /dev/null @@ -1,13 +0,0 @@ -:global(.webchat-fluent) .part-grouping-decorator { - .message-status-loader { - --webchat__component-icon--mask: unset; - --webchat__component-icon--image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22100%22%20height%3D%22100%22%20viewBox%3D%220%200%20100%20100%22%3E%3Cdefs%3E%3ClinearGradient%20id%3D%22a%22%20x1%3D%22-.5%22%20x2%3D%221.5%22%20y1%3D%22.5%22%20y2%3D%22.5%22%20gradientUnits%3D%22objectBoundingBox%22%3E%3Cstop%20offset%3D%220%22%20stop-color%3D%22%23d161aa%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%23e96d80%22%2F%3E%3C%2FlinearGradient%3E%3ClinearGradient%20id%3D%22b%22%20x1%3D%22-.5%22%20x2%3D%221.5%22%20y1%3D%22.5%22%20y2%3D%22.5%22%20gradientUnits%3D%22objectBoundingBox%22%3E%3Cstop%20offset%3D%220%22%20stop-color%3D%22%2342a3a6%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%2373af69%22%2F%3E%3C%2FlinearGradient%3E%3ClinearGradient%20id%3D%22c%22%20x1%3D%22-.5%22%20x2%3D%221.5%22%20y1%3D%22.5%22%20y2%3D%22.5%22%20gradientUnits%3D%22objectBoundingBox%22%3E%3Cstop%20offset%3D%220%22%20stop-color%3D%22%2365a5dd%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%238173ee%22%2F%3E%3C%2FlinearGradient%3E%3C%2Fdefs%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22url(%23a)%22%3E%3Canimate%20attributeName%3D%22r%22%20begin%3D%220s%22%20calcMode%3D%22spline%22%20dur%3D%223.6s%22%20keySplines%3D%220.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%22%20keyTimes%3D%220%3B0.1667%3B0.3333%3B0.5%3B0.6667%3B0.8333%3B1%22%20repeatCount%3D%22indefinite%22%20values%3D%2250%3B0%3B0%3B0%3B0%3B49%3B49%22%2F%3E%3Canimate%20attributeName%3D%22opacity%22%20begin%3D%220s%22%20calcMode%3D%22spline%22%20dur%3D%223.6s%22%20keySplines%3D%220.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%22%20keyTimes%3D%220%3B0.1667%3B0.3333%3B0.5%3B0.6667%3B0.6668%3B1%22%20repeatCount%3D%22indefinite%22%20values%3D%221%3B0%3B0%3B0%3B0%3B1%3B1%22%2F%3E%3Canimate%20attributeName%3D%22cx%22%20begin%3D%220s%22%20calcMode%3D%22spline%22%20dur%3D%223.6s%22%20keySplines%3D%220.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%22%20keyTimes%3D%220%3B0.1667%3B0.3333%3B0.5%3B0.6667%3B0.8333%3B1%22%20repeatCount%3D%22indefinite%22%20values%3D%2250%3B100%3B0%3B0%3B0%3B50%3B50%22%2F%3E%3C%2Fcircle%3E%3Ccircle%20cy%3D%2250%22%20r%3D%220%22%20fill%3D%22url(%23b)%22%20opacity%3D%220%22%3E%3Canimate%20attributeName%3D%22r%22%20begin%3D%220s%22%20calcMode%3D%22spline%22%20dur%3D%223.6s%22%20keySplines%3D%220.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%22%20keyTimes%3D%220%3B0.1667%3B0.3333%3B0.5%3B0.6667%3B0.8333%3B1%22%20repeatCount%3D%22indefinite%22%20values%3D%220%3B49%3B49%3B0%3B0%3B0%3B0%22%2F%3E%3Canimate%20attributeName%3D%22opacity%22%20begin%3D%220s%22%20calcMode%3D%22spline%22%20dur%3D%223.6s%22%20keySplines%3D%220.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%22%20keyTimes%3D%220%3B0.0001%3B0.3333%3B0.5%3B0.6667%3B0.8333%3B1%22%20repeatCount%3D%22indefinite%22%20values%3D%220%3B1%3B1%3B0%3B0%3B0%3B0%22%2F%3E%3Canimate%20attributeName%3D%22cx%22%20begin%3D%220s%22%20calcMode%3D%22spline%22%20dur%3D%223.6s%22%20keySplines%3D%220.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%22%20keyTimes%3D%220%3B0.1667%3B0.3333%3B0.5%3B0.6667%3B0.8333%3B1%22%20repeatCount%3D%22indefinite%22%20values%3D%220%3B50%3B50%3B100%3B0%3B0%3B0%22%2F%3E%3C%2Fcircle%3E%3Ccircle%20cy%3D%2250%22%20r%3D%220%22%20fill%3D%22url(%23c)%22%20opacity%3D%220%22%3E%3Canimate%20attributeName%3D%22r%22%20begin%3D%220s%22%20calcMode%3D%22spline%22%20dur%3D%223.6s%22%20keySplines%3D%220.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%22%20keyTimes%3D%220%3B0.1667%3B0.3333%3B0.5%3B0.6667%3B0.8333%3B1%22%20repeatCount%3D%22indefinite%22%20values%3D%220%3B0%3B0%3B49%3B49%3B0%3B0%22%2F%3E%3Canimate%20attributeName%3D%22opacity%22%20begin%3D%220s%22%20calcMode%3D%22spline%22%20dur%3D%223.6s%22%20keySplines%3D%220.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%22%20keyTimes%3D%220%3B0.1667%3B0.1668%3B0.5%3B0.6667%3B0.8333%3B1%22%20repeatCount%3D%22indefinite%22%20values%3D%220%3B0%3B0%3B1%3B1%3B0%3B0%22%2F%3E%3Canimate%20attributeName%3D%22cx%22%20begin%3D%220s%22%20calcMode%3D%22spline%22%20dur%3D%223.6s%22%20keySplines%3D%220.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%22%20keyTimes%3D%220%3B0.1667%3B0.3333%3B0.5%3B0.6667%3B0.8333%3B1%22%20repeatCount%3D%22indefinite%22%20values%3D%220%3B0%3B0%3B50%3B50%3B100%3B0%22%2F%3E%3C%2Fcircle%3E%3C%2Fsvg%3E'); - - animation: none; - color: transparent; - - @media (prefers-reduced-motion: reduce) { - --webchat__component-icon--image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22100%22%20height%3D%22100%22%20viewBox%3D%220%200%20100%20100%22%3E%3Cdefs%3E%3ClinearGradient%20id%3D%22a%22%20x1%3D%22-.5%22%20x2%3D%221.5%22%20y1%3D%22.5%22%20y2%3D%22.5%22%20gradientUnits%3D%22objectBoundingBox%22%3E%3Cstop%20offset%3D%220%22%20stop-color%3D%22%23d161aa%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%23e96d80%22%2F%3E%3C%2FlinearGradient%3E%3C%2Fdefs%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2249%22%20fill%3D%22url(%23a)%22%2F%3E%3C%2Fsvg%3E'); - } - } -} diff --git a/packages/fluent-theme/src/overrides/PartGroupingActivity.module.css b/packages/fluent-theme/src/overrides/PartGroupingActivity.module.css deleted file mode 100644 index 87dcb0e5b6..0000000000 --- a/packages/fluent-theme/src/overrides/PartGroupingActivity.module.css +++ /dev/null @@ -1,6 +0,0 @@ -:global(.webchat-fluent) .part-grouping-decorator { - .stacked-layout .collapsible-grouping__content .part-grouping-activity__activities { - --webchat--collapsible-grouping__list--gap: var(--webchat-spacingVerticalS); - padding: var(--webchat-spacingVerticalL) var(--webchat-spacingHorizontalL); - } -} diff --git a/packages/fluent-theme/src/overrides/RenderMarkdown.module.css b/packages/fluent-theme/src/overrides/RenderMarkdown.module.css deleted file mode 100644 index 867c9bcd63..0000000000 --- a/packages/fluent-theme/src/overrides/RenderMarkdown.module.css +++ /dev/null @@ -1,97 +0,0 @@ -:global(.webchat-fluent) .part-grouping-decorator { - .render-markdown a[href]:not(:global(.render-markdown__pure-identifier)) { - color: var(--webchat-colorBrandForegroundLink); - text-decoration-color: transparent; - - &:target { - color: var(--webchat-colorBrandForegroundLinkSelected); - } - - &:hover { - color: var(--webchat-colorBrandForegroundLinkHover); - text-decoration: underline 1px currentColor; - } - - &:active { - color: var(--webchat-colorBrandForegroundLinkPressed); - } - - &:focus-visible { - outline: none; - text-decoration: underline 1px double var(--webchat-colorStrokeFocus2); - } - - :global(.render-markdown__external-link-icon) { - background: currentColor; - -webkit-mask: var(--webchat__icon-url--external-link) no-repeat; - mask: var(--webchat__icon-url--external-link) no-repeat; - - @media (forced-colors: active) { - background: LinkText; - } - } - } - - .render-markdown :is(:global(.render-markdown__citation), :global(.render-markdown__pure-identifier)) { - align-items: center; - background-color: var(--webchat-colorNeutralBackground3); - border-radius: var(--webchat-borderRadiusMedium); - border: var(--webchat-strokeWidthThin) solid var(--webchat-colorNeutralStroke2); - box-sizing: border-box; - color: var(--webchat-colorNeutralForeground2); - display: inline-flex; - font-size: var(--webchat-fontSizeBase100); - font-weight: var(--webchat-fontWeightSemibold); - justify-content: center; - line-height: 12.4px; - margin-left: var(--webchat-spacingHorizontalXXS); - margin-right: var(--webchat-spacingHorizontalXXS); - min-height: 12.4px; - min-width: 14px; - text-decoration: none; - transition: all var(--webchat-durationNormal) var(--webchat-curveDecelerateMid); - vertical-align: calc((var(--webchat-lineHeightBase100) - var(--webchat-fontSizeBase100)) / 2); - - &:hover { - background-color: var(--webchat-colorBrandBackground2Hover); - border-color: var(--webchat-colorBrandStroke2Hover); - color: var(--webchat-colorBrandForeground2Hover); - cursor: pointer; - } - - &:hover:active { - background-color: var(--webchat-colorBrandBackground2Pressed); - border-color: var(--webchat-colorBrandStroke2Pressed); - color: var(--webchat-colorBrandForeground2Pressed); - } - - &::before, - &::after { - all: unset; - } - - &:has(:global(.render-markdown__external-link-icon)) { - padding-inline: 3px; - } - - :global(.render-markdown__external-link-icon) { - background: currentColor; - height: 0.7em; - -webkit-mask: var(--webchat__icon-url--external-link) no-repeat; - mask: var(--webchat__icon-url--external-link) no-repeat; - - @media (forced-colors: active) { - background: LinkText; - } - } - - @media (forced-colors: active) { - &, - &:hover, - &:hover:active { - border-color: LinkText; - color: LinkText; - } - } - } -} diff --git a/packages/fluent-theme/src/overrides/StackedLayout.module.css b/packages/fluent-theme/src/overrides/StackedLayout.module.css deleted file mode 100644 index e4819b6082..0000000000 --- a/packages/fluent-theme/src/overrides/StackedLayout.module.css +++ /dev/null @@ -1,176 +0,0 @@ -:global(.webchat-fluent) .part-grouping-decorator { - .collapsible-grouping__content .stacked-layout .stacked-layout__title, - .collapsible-grouping__title { - color: var(--webchat-colorNeutralForeground2); - font-size: var(--webchat-fontSizeBase300); - font-weight: var(--webchat-fontWeightSemibold); - line-height: var(--webchat-lineHeightBase300); - } - - .stacked-layout .stacked-layout__message-status { - --webchat__component-icon--size: 14px; - - color: var(--webchat-colorNeutralForeground2); - font-size: var(--webchat-fontSizeBase400); - } - - .collapsible-grouping { - .stacked-layout__message-status { - height: unset; - width: unset; - } - - .stacked-layout :global(.activity-loader) { - display: none; - } - } - .stacked-layout { - display: flex; - flex-flow: column nowrap; - margin-inline: var(--webchat-spacingHorizontalMNudge); - - .stacked-layout { - margin-inline: var(--webchat-spacingHorizontalNone); - position: static; - } - - .stacked-layout__main .stacked-layout__title { - color: var(--webchat-colorNeutralForeground4); - font-size: var(--webchat-fontSizeBase400); - line-height: var(--webchat-lineHeightBase400); - margin: var(--webchat-part-grouping__bubble--space-block) var(--webchat-part-grouping__bubble--space-inline); - } - - &.stacked-layout--no-message .stacked-layout__attachment-row:first-child { - margin-block-start: 0; - } - } - - .stacked-layout .stacked-layout__main .stacked-layout__message-status { - margin-block-start: var(--webchat-part-grouping__message-status--space-block); - } - - .stacked-layout .stacked-layout__status { - font-size: var(--webchat__font-size--small); - line-height: var(--webchat__line-height--small); - } - .stacked-layout .bubble .collapsible-content { - .collapsible-content__content { - .stacked-layout__attachment-list { - gap: var(--webchat-spacingVerticalS); - } - - .stacked-layout__attachment-row { - margin: 0; - } - } - } - &.part-grouping-decorator--group { - .stacked-layout .stacked-layout__main .stacked-layout__title, - .stacked-layout .stacked-layout__main .collapsible-content__summary { - margin: var(--webchat-spacingVerticalXS) var(--webchat-part-grouping__bubble--space-inline); - } - } - &.variant-fluent { - .stacked-layout .stacked-layout__attachment-row { - margin-block-start: var(--webchat-spacingVerticalMNudge); - } - - &.part-grouping-decorator--group { - .collapsible-grouping { - .collapsible-grouping__header .stacked-layout__message-status { - margin-block-start: var(--webchat-spacingVerticalNone); - margin-inline-start: var(--webchat-spacingHorizontalNone); - } - - .stacked-layout .stacked-layout__message-status { - margin-inline-end: calc(var(--webchat-spacingHorizontalS) * -1); - } - } - } - } - &.variant-copilot { - &.part-grouping-decorator--from-bot { - .stacked-layout { - margin: 0; - position: static; - } - - .bubble .collapsible-content .collapsible-content__content { - .stacked-layout__attachment-list { - margin-block-start: var(--webchat-spacingVerticalS); - } - - .stacked-layout__attachment { - max-width: var(--webchat-part-grouping__bubble--max-width); - } - } - - .stacked-layout__status { - display: none; - } - } - - &.part-grouping-decorator--group { - .bubble .bubble__content { - .stacked-layout__bubble .stacked-layout__title, - .stacked-layout__bubble .collapsible-content__summary { - color: var(--webchat-colorNeutralForeground2); - margin-block: var(--webchat-spacingVerticalXXS); - } - - :global(.stacked-loyout__bubble) .stacked-layout__message-status, - .stacked-layout__bubble .stacked-layout__title, - .stacked-layout__bubble .collapsible-content__summary, - .stacked-layout__bubble .text-content { - font-size: var(--webchat-fontSizeBase200); - line-height: var(--webchat-lineHeightBase200); - } - } - } - - .stacked-layout__status { - order: -1; - } - - .stacked-layout { - margin-inline: var(--webchat-spacingHorizontalSNudge); - } - - .collapsible-grouping__content:not(.collapsible-grouping__content--open) .collapsible-grouping__list { - > *:not(:has(.stacked-layout__message-status--incomplete)) { - height: 0; - margin: 0; - visibility: hidden; - overflow: clip; - } - - > *:has(.stacked-layout__message-status--incomplete) { - /* We use animation so we can reset needed properties immediately upon switching classes */ - animation: collapsible-grouping__fade-in var(--webchat__transition-duration) var(--webchat__transition-duration) - var(--webchat__transition-easing) both; - pointer-events: none; - - .stacked-layout__message-status--incomplete, - .collapsible-content__chevron { - visibility: hidden; - } - } - - &:has(.stacked-layout__message-status--incomplete) { - padding: var(--webchat-spacingVerticalNone) var(--webchat-spacingHorizontalNone); - transform: translate(-4px, -20px); - margin-block-end: calc(-20px - var(--webchat--collapsible-grouping__list--gap)); - } - } - - .stacked-layout .stacked-layout__message-status { - --webchat__component-icon--size: 14px; - - color: var(--webchat-colorNeutralForeground2); - height: 20px; - margin-inline: var(--webchat-spacingHorizontalNone) var(--webchat-spacingHorizontalS); - width: 20px; - } - } -} diff --git a/packages/fluent-theme/src/overrides/TextContent.module.css b/packages/fluent-theme/src/overrides/TextContent.module.css deleted file mode 100644 index 10b00e7da6..0000000000 --- a/packages/fluent-theme/src/overrides/TextContent.module.css +++ /dev/null @@ -1,53 +0,0 @@ -:global(.webchat-fluent) .part-grouping-decorator { - .stacked-layout .bubble .text-content__generated-badge { - align-items: center; - align-self: flex-start; - background-color: var(--webchat-colorNeutralBackground5); - border-radius: var(--webchat-borderRadiusMedium); - box-sizing: border-box; - color: var(--webchat-colorNeutralForeground3); - cursor: default; - display: inline-flex; - font-size: var(--webchat-fontSizeBase100); - height: 16px; - line-height: var(--webchat-lineHeightBase100); - padding-inline: var(--webchat-spacingHorizontalXS); - } - - .stacked-layout .bubble .text-content { - font-size: var(--webchat__font-size--medium); - line-height: var(--webchat__line-height--medium); - min-height: auto; - padding: var(--webchat-part-grouping__text-content--space-block-inline); - - &:empty { - padding-block-end: 0; - } - - + .text-content { - margin-top: calc(var(--webchat-part-grouping__bubble--space-block) * -1); - } - } - .stacked-layout .bubble .collapsible-content { - .collapsible-content__content { - .stacked-layout__attachment-row { - .text-content { - padding-block: 0; - } - } - } - } - &.variant-copilot { - &.part-grouping-decorator--group { - .bubble .bubble__content { - .stacked-layout__bubble .text-content { - color: var(--webchat-colorNeutralForeground3); - } - } - } - - .bubble .text-content .text-content__generated-badge { - display: none; - } - } -} diff --git a/packages/fluent-theme/src/overrides/ThumbButton.module.css b/packages/fluent-theme/src/overrides/ThumbButton.module.css deleted file mode 100644 index 213f3803de..0000000000 --- a/packages/fluent-theme/src/overrides/ThumbButton.module.css +++ /dev/null @@ -1,15 +0,0 @@ -/* Feedback button */ -:global(.webchat-fluent).theme .thumb-button, -:global(.webchat-fluent).theme .thumb-button.thumb-button--large { - .thumb-button__image { - color: var(--webchat-colorNeutralForeground1); - } - - &:has(.thumb-button__input:focus-visible) { - outline: var(--webchat-strokeWidthThick) solid var(--webchat-colorStrokeFocus2); - } - - &:has(.thumb-button__input[aria-disabled='true']) .thumb-button__image { - color: var(--webchat-colorNeutralForegroundDisabled); - } -} diff --git a/packages/fluent-theme/src/overrides/TranscriptFocus.module.css b/packages/fluent-theme/src/overrides/TranscriptFocus.module.css deleted file mode 100644 index be73802ea2..0000000000 --- a/packages/fluent-theme/src/overrides/TranscriptFocus.module.css +++ /dev/null @@ -1,68 +0,0 @@ -:global(.webchat-fluent) .part-grouping-decorator { - &.variant-fluent { - .stacked-layout .transcript-focus-area__activity-list { - --webchat--collapsible-grouping__list--gap: var(--webchat-spacingVerticalXS); - padding: var(--webchat-spacingVerticalNone) var(--webchat-spacingHorizontalNone); - gap: var(--webchat-spacingVerticalXS); - } - } - &.variant-copilot { - .stacked-layout .transcript-focus-area__activity-list { - --webchat--collapsible-grouping__list--gap: var(--webchat-spacingVerticalS); - padding: var(--webchat-spacingVerticalM) var(--webchat-spacingHorizontalM); - } - } -} -:global(.webchat-fluent).theme.variant-copilot .transcript-focus-area { - --webchat__transcript--spacing: var(--webchat-spacingVerticalM); -} - -/* Transcript focus indicator color */ -:global(.webchat-fluent).theme .transcript-focus-area__transcript-indicator { - --webchat__color--transcript-visual-keyboard-indicator: var(--webchat-colorStrokeFocus2); -} - -/* Transcript Content indicator without focus-visible state */ -:global(.webchat-fluent).theme .transcript-focus-area:not(:focus-visible) .transcript-focus-area__indicator { - /* Hide activity focus when no focus-visible state */ - display: none; -} - -/* Transcript Focus indicator when content focused */ -:global(.webchat-fluent).theme .transcript-focus-area:focus-visible:has(.transcript-focus-area__content--focused) { - .transcript-focus-area__transcript-indicator { - /* Hide transcript focus when an activity is focused */ - display: none; - } -} -:global(.webchat-fluent).theme .transcript-focus-area:focus-visible .transcript-focus-area__content--focused { - .transcript-focus-area__indicator { - /* Hide transcript activity focus when the content is focused */ - display: none; - } - - &:has(.collapsible-grouping) > .transcript-focus-area__content-overlay .transcript-focus-area__indicator { - display: none; - } - - :global(.pre-chat-message-activity)::after { - border-radius: var(--webchat-part-grouping__bubble--border-radius); - content: ''; - inset: 0; - outline-offset: 0; - outline: var(--webchat-strokeWidthThick) solid var(--webchat-colorStrokeFocus2); - pointer-events: none; - position: absolute; - } - - :global(.pre-chat-message-activity)::after { - border-radius: var(--webchat-borderRadiusMedium); - outline-offset: -3px; - } - - :global(.liner-message-activity__text) { - border-radius: 20px; - outline-offset: 4px; - outline: var(--webchat-strokeWidthThick) solid var(--webchat-colorStrokeFocus2); - } -} diff --git a/packages/fluent-theme/src/overrides/ViewCodeDialog.module.css b/packages/fluent-theme/src/overrides/ViewCodeDialog.module.css deleted file mode 100644 index 37baaf8c99..0000000000 --- a/packages/fluent-theme/src/overrides/ViewCodeDialog.module.css +++ /dev/null @@ -1,64 +0,0 @@ -:global(.webchat-fluent).theme :global(.webchat__modal-dialog).view-code-dialog { - :global(.webchat__modal-dialog__close-button-layout) { - display: flex; - padding: var(--webchat-spacingVerticalS) var(--webchat-spacingHorizontalS); - } - - :global(.webchat__modal-dialog__close-button) { - align-items: center; - aspect-ratio: 1; - display: flex; - height: var(--webchat-lineHeightBase300); - justify-content: center; - padding: 0; - width: unset; - } - - :global(.webchat__modal-dialog__box) { - width: fit-content; - } - - :global(.webchat__modal-dialog__body) { - margin: 0; - padding: var(--webchat-spacingVerticalS) var(--webchat-spacingHorizontalM); - } - - .view-code-dialog__title { - font-size: var(--webchat-fontSizeBase300); - font-weight: var(--webchat-fontWeightSemibold); - line-height: var(--webchat-lineHeightBase300); - } - - .view-code-dialog__body { - font-family: var(--webchat-fontFamilyMonospace); - font-size: var(--webchat-fontSizeBase300); - margin: 0 calc(var(--webchat-spacingHorizontalM) * -1); - padding: 0 var(--webchat-spacingHorizontalM); - } - - .view-code-dialog__footer { - color: var(--webchat-colorNeutralForeground4); - font-size: var(--webchat-fontSizeBase100); - line-height: var(--webchat-lineHeightBase100); - } - - :global(.view-code-dialog__link) { - color: var(--webchat-colorBrandForegroundLink); - text-decoration-color: transparent; - - &:target { - color: var(--webchat-colorBrandForegroundLinkSelected); - } - &:hover { - color: var(--webchat-colorBrandForegroundLinkHover); - text-decoration: underline 1px currentColor; - } - &:active { - color: var(--webchat-colorBrandForegroundLinkPressed); - } - &:focus-visible { - outline: none; - text-decoration: underline 1px double var(--webchat-colorStrokeFocus2); - } - } -} diff --git a/packages/fluent-theme/src/private/fluentCSSModuleTheme.module.css b/packages/fluent-theme/src/private/fluentCSSModuleTheme.module.css new file mode 100644 index 0000000000..f7b4e3775d --- /dev/null +++ b/packages/fluent-theme/src/private/fluentCSSModuleTheme.module.css @@ -0,0 +1,1456 @@ +/* #region PartGroupingDecorator */ + +:global(.webchat-fluent) .part-grouping-decorator { + /* #region Generated badge & attachments */ + .stacked-layout .bubble .text-content__generated-badge { + align-items: center; + align-self: flex-start; + background-color: var(--webchat-colorNeutralBackground5); + border-radius: var(--webchat-borderRadiusMedium); + box-sizing: border-box; + color: var(--webchat-colorNeutralForeground3); + cursor: default; + display: inline-flex; + font-size: var(--webchat-fontSizeBase100); + height: 16px; + line-height: var(--webchat-lineHeightBase100); + padding-inline: var(--webchat-spacingHorizontalXS); + } + + .message-status-loader { + --webchat__component-icon--mask: unset; + --webchat__component-icon--image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22100%22%20height%3D%22100%22%20viewBox%3D%220%200%20100%20100%22%3E%3Cdefs%3E%3ClinearGradient%20id%3D%22a%22%20x1%3D%22-.5%22%20x2%3D%221.5%22%20y1%3D%22.5%22%20y2%3D%22.5%22%20gradientUnits%3D%22objectBoundingBox%22%3E%3Cstop%20offset%3D%220%22%20stop-color%3D%22%23d161aa%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%23e96d80%22%2F%3E%3C%2FlinearGradient%3E%3ClinearGradient%20id%3D%22b%22%20x1%3D%22-.5%22%20x2%3D%221.5%22%20y1%3D%22.5%22%20y2%3D%22.5%22%20gradientUnits%3D%22objectBoundingBox%22%3E%3Cstop%20offset%3D%220%22%20stop-color%3D%22%2342a3a6%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%2373af69%22%2F%3E%3C%2FlinearGradient%3E%3ClinearGradient%20id%3D%22c%22%20x1%3D%22-.5%22%20x2%3D%221.5%22%20y1%3D%22.5%22%20y2%3D%22.5%22%20gradientUnits%3D%22objectBoundingBox%22%3E%3Cstop%20offset%3D%220%22%20stop-color%3D%22%2365a5dd%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%238173ee%22%2F%3E%3C%2FlinearGradient%3E%3C%2Fdefs%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22url(%23a)%22%3E%3Canimate%20attributeName%3D%22r%22%20begin%3D%220s%22%20calcMode%3D%22spline%22%20dur%3D%223.6s%22%20keySplines%3D%220.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%22%20keyTimes%3D%220%3B0.1667%3B0.3333%3B0.5%3B0.6667%3B0.8333%3B1%22%20repeatCount%3D%22indefinite%22%20values%3D%2250%3B0%3B0%3B0%3B0%3B49%3B49%22%2F%3E%3Canimate%20attributeName%3D%22opacity%22%20begin%3D%220s%22%20calcMode%3D%22spline%22%20dur%3D%223.6s%22%20keySplines%3D%220.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%22%20keyTimes%3D%220%3B0.1667%3B0.3333%3B0.5%3B0.6667%3B0.6668%3B1%22%20repeatCount%3D%22indefinite%22%20values%3D%221%3B0%3B0%3B0%3B0%3B1%3B1%22%2F%3E%3Canimate%20attributeName%3D%22cx%22%20begin%3D%220s%22%20calcMode%3D%22spline%22%20dur%3D%223.6s%22%20keySplines%3D%220.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%22%20keyTimes%3D%220%3B0.1667%3B0.3333%3B0.5%3B0.6667%3B0.8333%3B1%22%20repeatCount%3D%22indefinite%22%20values%3D%2250%3B100%3B0%3B0%3B0%3B50%3B50%22%2F%3E%3C%2Fcircle%3E%3Ccircle%20cy%3D%2250%22%20r%3D%220%22%20fill%3D%22url(%23b)%22%20opacity%3D%220%22%3E%3Canimate%20attributeName%3D%22r%22%20begin%3D%220s%22%20calcMode%3D%22spline%22%20dur%3D%223.6s%22%20keySplines%3D%220.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%22%20keyTimes%3D%220%3B0.1667%3B0.3333%3B0.5%3B0.6667%3B0.8333%3B1%22%20repeatCount%3D%22indefinite%22%20values%3D%220%3B49%3B49%3B0%3B0%3B0%3B0%22%2F%3E%3Canimate%20attributeName%3D%22opacity%22%20begin%3D%220s%22%20calcMode%3D%22spline%22%20dur%3D%223.6s%22%20keySplines%3D%220.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%22%20keyTimes%3D%220%3B0.0001%3B0.3333%3B0.5%3B0.6667%3B0.8333%3B1%22%20repeatCount%3D%22indefinite%22%20values%3D%220%3B1%3B1%3B0%3B0%3B0%3B0%22%2F%3E%3Canimate%20attributeName%3D%22cx%22%20begin%3D%220s%22%20calcMode%3D%22spline%22%20dur%3D%223.6s%22%20keySplines%3D%220.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%22%20keyTimes%3D%220%3B0.1667%3B0.3333%3B0.5%3B0.6667%3B0.8333%3B1%22%20repeatCount%3D%22indefinite%22%20values%3D%220%3B50%3B50%3B100%3B0%3B0%3B0%22%2F%3E%3C%2Fcircle%3E%3Ccircle%20cy%3D%2250%22%20r%3D%220%22%20fill%3D%22url(%23c)%22%20opacity%3D%220%22%3E%3Canimate%20attributeName%3D%22r%22%20begin%3D%220s%22%20calcMode%3D%22spline%22%20dur%3D%223.6s%22%20keySplines%3D%220.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%22%20keyTimes%3D%220%3B0.1667%3B0.3333%3B0.5%3B0.6667%3B0.8333%3B1%22%20repeatCount%3D%22indefinite%22%20values%3D%220%3B0%3B0%3B49%3B49%3B0%3B0%22%2F%3E%3Canimate%20attributeName%3D%22opacity%22%20begin%3D%220s%22%20calcMode%3D%22spline%22%20dur%3D%223.6s%22%20keySplines%3D%220.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%22%20keyTimes%3D%220%3B0.1667%3B0.1668%3B0.5%3B0.6667%3B0.8333%3B1%22%20repeatCount%3D%22indefinite%22%20values%3D%220%3B0%3B0%3B1%3B1%3B0%3B0%22%2F%3E%3Canimate%20attributeName%3D%22cx%22%20begin%3D%220s%22%20calcMode%3D%22spline%22%20dur%3D%223.6s%22%20keySplines%3D%220.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%3B0.8%200%200.2%201%22%20keyTimes%3D%220%3B0.1667%3B0.3333%3B0.5%3B0.6667%3B0.8333%3B1%22%20repeatCount%3D%22indefinite%22%20values%3D%220%3B0%3B0%3B50%3B50%3B100%3B0%22%2F%3E%3C%2Fcircle%3E%3C%2Fsvg%3E'); + + animation: none; + color: transparent; + + @media (prefers-reduced-motion: reduce) { + --webchat__component-icon--image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22100%22%20height%3D%22100%22%20viewBox%3D%220%200%20100%20100%22%3E%3Cdefs%3E%3ClinearGradient%20id%3D%22a%22%20x1%3D%22-.5%22%20x2%3D%221.5%22%20y1%3D%22.5%22%20y2%3D%22.5%22%20gradientUnits%3D%22objectBoundingBox%22%3E%3Cstop%20offset%3D%220%22%20stop-color%3D%22%23d161aa%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%23e96d80%22%2F%3E%3C%2FlinearGradient%3E%3C%2Fdefs%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2249%22%20fill%3D%22url(%23a)%22%2F%3E%3C%2Fsvg%3E'); + } + } + + .stacked-layout .bubble :global(.webchat__fileContent__badge) { + cursor: default; + font-size: var(--webchat-fontSizeBase300); + line-height: var(--webchat-lineHeightBase300); + + :global(.webchat__fileContent__fileName) { + color: var(--webchat-colorBrandForegroundLink); + font-family: inherit; + } + + :global(.webchat__fileContent__size) { + color: var(--webchat-colorNeutralForeground2); + font-family: inherit; + } + } + + .stacked-layout .bubble :global(.webchat__fileContent__downloadIcon) { + color: var(--webchat-colorBrandForegroundLink); + } + /* #endregion */ + + /* #region Markdown & citations */ + .render-markdown a[href]:not(:global(.render-markdown__pure-identifier)) { + color: var(--webchat-colorBrandForegroundLink); + text-decoration-color: transparent; + + &:target { + color: var(--webchat-colorBrandForegroundLinkSelected); + } + + &:hover { + color: var(--webchat-colorBrandForegroundLinkHover); + text-decoration: underline 1px currentColor; + } + + &:active { + color: var(--webchat-colorBrandForegroundLinkPressed); + } + + &:focus-visible { + outline: none; + text-decoration: underline 1px double var(--webchat-colorStrokeFocus2); + } + + :global(.render-markdown__external-link-icon) { + background: currentColor; + -webkit-mask: var(--webchat__icon-url--external-link) no-repeat; + mask: var(--webchat__icon-url--external-link) no-repeat; + + @media (forced-colors: active) { + background: LinkText; + } + } + } + + .render-markdown :is(:global(.render-markdown__citation), :global(.render-markdown__pure-identifier)) { + align-items: center; + background-color: var(--webchat-colorNeutralBackground3); + border-radius: var(--webchat-borderRadiusMedium); + border: var(--webchat-strokeWidthThin) solid var(--webchat-colorNeutralStroke2); + box-sizing: border-box; + color: var(--webchat-colorNeutralForeground2); + display: inline-flex; + font-size: var(--webchat-fontSizeBase100); + font-weight: var(--webchat-fontWeightSemibold); + justify-content: center; + line-height: 12.4px; + margin-left: var(--webchat-spacingHorizontalXXS); + margin-right: var(--webchat-spacingHorizontalXXS); + min-height: 12.4px; + min-width: 14px; + text-decoration: none; + transition: all var(--webchat-durationNormal) var(--webchat-curveDecelerateMid); + vertical-align: calc((var(--webchat-lineHeightBase100) - var(--webchat-fontSizeBase100)) / 2); + + &:hover { + background-color: var(--webchat-colorBrandBackground2Hover); + border-color: var(--webchat-colorBrandStroke2Hover); + color: var(--webchat-colorBrandForeground2Hover); + cursor: pointer; + } + + &:hover:active { + background-color: var(--webchat-colorBrandBackground2Pressed); + border-color: var(--webchat-colorBrandStroke2Pressed); + color: var(--webchat-colorBrandForeground2Pressed); + } + + &::before, + &::after { + all: unset; + } + + &:has(:global(.render-markdown__external-link-icon)) { + padding-inline: 3px; + } + + :global(.render-markdown__external-link-icon) { + background: currentColor; + height: 0.7em; + -webkit-mask: var(--webchat__icon-url--external-link) no-repeat; + mask: var(--webchat__icon-url--external-link) no-repeat; + + @media (forced-colors: active) { + background: LinkText; + } + } + + @media (forced-colors: active) { + &, + &:hover, + &:hover:active { + border-color: LinkText; + color: LinkText; + } + } + } + /* #endregion */ + + /* #region Citation summary */ + .link-definitions__header { + border-radius: var(--webchat-borderRadiusMedium); + + .link-definitions__header-text { + color: var(--webchat-colorNeutralForeground3); + } + + .link-definitions__message-sensitivity-label-text { + color: var(--webchat-colorNeutralForeground4); + } + + .link-definitions__header-chevron { + color: var(--webchat-colorNeutralForeground3); + font-size: var(--webchat__font-size--small); + } + + &:focus-visible { + outline-offset: 1px; + outline: var(--webchat-strokeWidthThin) solid var(--webchat-colorStrokeFocus2); + } + } + + .link-definitions { + --webchat__citation__external-link--mask: var(--webchat-externalLink-mask); + --webchat__citation__link--max-width: var(--webchat-externalLink-maxWidth); + + .link-definitions__list { + color: var(--webchat__color--subtle); + display: flex; + flex-flow: row wrap; + gap: var(--webchat-spacingHorizontalS); + } + + .link-definitions__list-item { + border-radius: var(--webchat-borderRadiusMedium); + max-width: var(--webchat__citation__link--max-width); + } + + .link-definitions__list-item-box { + background-color: var(--webchat-colorNeutralBackground3); + border-radius: var(--webchat-borderRadiusMedium); + border: var(--webchat-strokeWidthThin) solid var(--webchat-colorNeutralStroke2); + box-sizing: border-box; + color: currentColor; + display: inline-flex; + font-size: var(--webchat__font-size--small); + height: 24px; + padding-inline-end: var(--webchat-spacingHorizontalS); + + /* Button chips use the link color like link chips; the badge, divider and text inherit it. */ + @media (forced-colors: active) { + border-color: LinkText; + color: LinkText; + } + } + + .link-definitions__list-item-body { + font-family: inherit; + gap: var(--webchat-spacingHorizontalSNudge); + min-width: 0; + padding: 0; + } + + .link-definitions__badge { + align-self: center; + background-color: transparent; + border-radius: 0; + border: none; + color: currentColor; + font-size: var(--webchat-fontSizeBase100); + font-weight: var(--webchat-fontWeightSemibold); + line-height: var(--webchat-lineHeightBase100); + margin: 0; + min-width: 20px; + padding: 0; + position: relative; + text-align: center; + } + + .link-definitions__badge::after { + border-right: var(--webchat-strokeWidthThin) solid var(--webchat-colorNeutralStroke2); + bottom: 0; + content: ''; + display: block; + height: 16px; + position: absolute; + right: 0; + top: 0; + width: 0; + } + + .link-definitions__list-item-main-text { + gap: var(--webchat-spacingHorizontalXS); + } + + .link-definitions__list-item-text { + color: currentColor; + text-decoration: none; + text-overflow: ellipsis; + } + + .link-definitions__list-item-main-text:has(.link-definitions__open-in-new-window-icon)::before { + align-self: center; + background: currentColor; + color: currentColor; + content: ''; + flex: none; + font-size: var(--webchat-fontSizeBase400); + height: 1em; + -webkit-mask: var(--webchat__citation__external-link--mask) no-repeat; + mask: var(--webchat__citation__external-link--mask) no-repeat; + padding: 0; + width: 1em; + + @media (forced-colors: active) { + background: LinkText; + } + } + + /* It seems Copilot does not show per-item sensitivity label, i.e. "General", etc. */ + .link-definitions__list-item-badge, + .link-definitions__open-in-new-window-icon { + display: none; + } + } + /* #endregion */ + + /* #region Header & collapsible grouping */ + .stacked-layout .collapsible-grouping__header { + justify-content: flex-start; + min-width: 0; + padding: var(--webchat-spacingVerticalNone) var(--webchat-spacingHorizontalNone); + place-self: start; + position: relative; + + .activity-button { + background: transparent; + border: none; + cursor: pointer; + font-size: var(--webchat-fontSizeBase300); + padding: var(--webchat-spacingVerticalXXS) var(--webchat-spacingHorizontalXS); + + &::after { + content: ''; + cursor: pointer; + display: block; + inset: 0; + position: absolute; + } + } + + .activity-button__text { + font-size: 0; + height: 0; + overflow: clip; + position: absolute; + width: 0; + } + } + + .stacked-layout .collapsible-grouping__content .part-grouping-activity__activities { + --webchat--collapsible-grouping__list--gap: var(--webchat-spacingVerticalS); + padding: var(--webchat-spacingVerticalL) var(--webchat-spacingHorizontalL); + } + + .stacked-layout .collapsible-grouping { + display: grid; + + &.collapsible-grouping--open { + gap: var(--webchat-spacingVerticalM); + } + } + + .collapsible-grouping__content .stacked-layout .stacked-layout__title, + .collapsible-grouping__title { + color: var(--webchat-colorNeutralForeground2); + font-size: var(--webchat-fontSizeBase300); + font-weight: var(--webchat-fontWeightSemibold); + line-height: var(--webchat-lineHeightBase300); + } + + .collapsible-grouping, + .collapsible-grouping__header { + border: none; + } + + .stacked-layout .stacked-layout__message-status { + --webchat__component-icon--size: 14px; + + color: var(--webchat-colorNeutralForeground2); + font-size: var(--webchat-fontSizeBase400); + } + + .collapsible-grouping { + max-width: 100%; + + .stacked-layout__message-status { + height: unset; + width: unset; + + .component-icon { + height: 19px; + width: 19px; + } + } + + .stacked-layout :global(.activity-loader) { + display: none; + } + + :global(.part-grouping-decorator) .bubble .bubble__content { + box-sizing: content-box; + gap: var(--webchat-spacingVerticalXS); + } + } + /* #endregion */ + + /* #region Stacked layout & message status */ + .stacked-layout { + display: flex; + flex-flow: column nowrap; + margin-inline: var(--webchat-spacingHorizontalMNudge); + + .stacked-layout { + margin-inline: var(--webchat-spacingHorizontalNone); + position: static; + } + + .stacked-layout__main .stacked-layout__title { + color: var(--webchat-colorNeutralForeground4); + font-size: var(--webchat-fontSizeBase400); + line-height: var(--webchat-lineHeightBase400); + margin: var(--webchat-part-grouping__bubble--space-block) var(--webchat-part-grouping__bubble--space-inline); + } + + &.stacked-layout--no-message .stacked-layout__attachment-row:first-child { + margin-block-start: 0; + } + } + + .stacked-layout .stacked-layout__main .stacked-layout__message-status { + margin-block-start: var(--webchat-part-grouping__message-status--space-block); + } + + .stacked-layout .stacked-layout__content:has(.bubble) { + max-width: 100%; + overflow: visible; + } + + .stacked-layout .stacked-layout__status { + font-size: var(--webchat__font-size--small); + line-height: var(--webchat__line-height--small); + } + /* #endregion */ + + /* #region Carousel layout */ + .carousel-layout { + .carousel-filmstrip__avatar-gutter, + .carousel-filmstrip__alignment-pad { + margin: 0; + width: var(--webchat-spacingHorizontalMNudge); + } + } + /* #endregion */ + + /* #region Bubble surface */ + .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; + } + + .stacked-layout .bubble:has(:global(.activity-loader)) .bubble__content { + background: unset; + box-shadow: unset; + border-color: transparent; + } + + .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); + border-width: 0; + box-shadow: var(--webchat-part-grouping__bubble-content--shadow); + box-sizing: border-box; + color: var(--webchat-colorNeutralForeground1); + max-width: 100%; + min-height: var(--webchat-part-grouping__bubble--min-height); + } + + /* Forced colors remove the bubble shadow and fill, so draw a border as the default theme does. */ + @media (forced-colors: active) { + &:not(.part-grouping-decorator--group) + .stacked-layout + .bubble:not(:has(:global(.activity-loader))) + .bubble__content { + border: var(--webchat-strokeWidthThin) solid CanvasText; + } + } + + .stacked-layout .bubble .text-content { + font-size: var(--webchat__font-size--medium); + line-height: var(--webchat__line-height--medium); + min-height: auto; + padding: var(--webchat-part-grouping__text-content--space-block-inline); + + &:empty { + padding-block-end: 0; + } + + + .text-content { + margin-top: calc(var(--webchat-part-grouping__bubble--space-block) * -1); + } + } + /* #endregion */ + + /* #region Code blocks */ + .stacked-layout .bubble .code-block-content { + border-radius: var(--webchat-borderRadiusXLarge); + border: var(--webchat-strokeWidthThin) solid var(--webchat-colorNeutralStroke1); + font-size: var(--webchat-fontSizeBase300); + font-weight: var(--webchat-fontWeightRegular); + margin-block: var(--webchat-spacingVerticalNone); + margin-inline: var(--webchat-part-grouping__code-block--space-inline); + + .code-block-content__header { + padding: var(--webchat-spacingVerticalM) var(--webchat-spacingHorizontalL); + } + + .code-block-content__code-block { + padding-block: var(--webchat-spacingVerticalM); + padding-inline: var(--webchat-spacingHorizontalL) var(--webchat-spacingHorizontalS); + } + + .code-block-copy-button { + --webchat__code-block__copy-button--color: var(--webchat-colorNeutralForeground1); + --webchat__code-block__copy-button--background: var(--webchat-colorNeutralBackground3); + + margin-block-start: var(--webchat-spacingVerticalM); + margin-inline-end: var(--webchat-spacingHorizontalL); + position: absolute; + right: 0; + top: 0; + } + } + /* #endregion */ + + /* #region Collapsible content */ + .stacked-layout .bubble .collapsible-content { + .collapsible-content__summary { + &:focus-visible { + border-radius: var(--webchat-borderRadiusSmall); + outline-offset: 4px; + outline: var(--webchat-strokeWidthThick) solid var(--webchat-colorStrokeFocus2); + } + } + + .collapsible-content__content { + margin-block: var(--webchat-part-grouping__collapsible-content--space-block-start-end); + + .stacked-layout__attachment-list { + gap: var(--webchat-spacingVerticalS); + } + + .stacked-layout__attachment-row { + margin: 0; + + .text-content { + padding-block: 0; + } + } + } + } + /* #endregion */ + + /* #region Group modifier */ + &.part-grouping-decorator--group { + .stacked-layout .stacked-layout__main .stacked-layout__title, + .stacked-layout .stacked-layout__main .collapsible-content__summary { + margin: var(--webchat-spacingVerticalXS) var(--webchat-part-grouping__bubble--space-inline); + } + } + /* #endregion */ + + /* #region Variant: Fluent */ + &.variant-fluent { + .stacked-layout .transcript-focus-area__activity-list { + --webchat--collapsible-grouping__list--gap: var(--webchat-spacingVerticalXS); + padding: var(--webchat-spacingVerticalNone) var(--webchat-spacingHorizontalNone); + gap: var(--webchat-spacingVerticalXS); + } + + .stacked-layout .stacked-layout__attachment-row { + margin-block-start: var(--webchat-spacingVerticalMNudge); + } + + &.part-grouping-decorator--group { + .collapsible-grouping { + anchor-name: --webchat-flair; + border-radius: var(--webchat-part-grouping__bubble--border-radius); + max-width: min(var(--webchat-part-grouping__bubble--max-width), 100%); + padding: var(--webchat-part-grouping__bubble--space-block) var(--webchat-part-grouping__bubble--space-inline); + position: relative; + transition: + background-color var(--webchat-durationNormal) var(--webchat-curveDecelerateMid), + box-shadow var(--webchat-durationNormal) var(--webchat-curveDecelerateMid); + + .collapsible-grouping__header .stacked-layout__message-status { + margin-block-start: var(--webchat-spacingVerticalNone); + margin-inline-start: var(--webchat-spacingHorizontalNone); + } + + .stacked-layout .stacked-layout__message-status { + margin-inline-end: calc(var(--webchat-spacingHorizontalS) * -1); + } + } + + &:has(.collapsible-grouping--open) { + .collapsible-grouping { + background-color: var(--webchat-part-grouping__bubble--background-color); + box-shadow: var(--webchat-part-grouping__bubble--box-shadow); + } + } + + .bubble { + position: static; + } + + .border-flair { + border-radius: var(--webchat-part-grouping__bubble--border-radius); + } + + .bubble .bubble__content { + margin-block: calc(var(--webchat-spacingVerticalS) * -1); + padding-block: var(--webchat-spacingVerticalS); + } + } + } + /* #endregion */ + + /* #region Variant: copilot */ + &.variant-copilot { + &.part-grouping-decorator--from-bot { + .stacked-layout { + margin: 0; + position: static; + } + + .carousel-layout { + .carousel-filmstrip__avatar-gutter, + .carousel-filmstrip__alignment-pad { + width: 0; + } + } + + .bubble { + position: static; + width: var(--webchat-part-grouping__bubble--max-width); + + .bubble__content .adaptive-card-renderer > :global(.ac-adaptiveCard) { + font-family: var(--webchat-fontFamilyBase); + /* Remove double padding for adaptive cards inside bubble */ + padding: unset !important; + + :global(.ac-textBlock) { + font-family: var(--webchat-fontFamilyBase) !important; + } + + :global(.ac-container:not([style*='background-color:']) .ac-textBlock), + > :global(.ac-textBlock) { + color: currentColor !important; + } + + :global(.ac-pushButton) { + background-color: var(--webchat-colorNeutralBackground1); + color: var(--webchat-colorNeutralForeground1); + border: var(--webchat-strokeWidthThin) solid var(--webchat-colorNeutralStroke1); + border-radius: var(--webchat-borderRadiusLarge); + transition: all var(--webchat-durationNormal) var(--webchat-curveDecelerateMid); + } + + :global(.ac-pushButton:hover) { + background-color: var(--webchat-colorNeutralBackground1Hover); + border-color: var(--webchat-colorNeutralStroke1Hover); + color: var(--webchat-colorNeutralForeground1Hover); + cursor: pointer; + } + + :global(.ac-pushButton:active), + :global(.ac-pushButton).adaptive-card-renderer__action--selected { + background-color: var(--webchat-colorNeutralBackground1Pressed); + border-color: var(--webchat-colorNeutralStroke1Pressed); + color: var(--webchat-colorNeutralForeground1Pressed); + outline-style: none; + } + + :global(.ac-pushButton:focus-visible) { + border-color: var(--webchat-colorStrokeFocus2); + border-width: var(--webchat-strokeWidthThin); + outline: var(--webchat-strokeWidthThick) solid var(--webchat-colorTransparentStroke); + box-shadow: 0 0 0 var(--webchat-strokeWidthThin) var(--webchat-colorStrokeFocus2) inset; + } + + :global(.ac-pushButton.primary) { + background-color: var(--webchat-colorBrandBackground); + border-color: transparent; + color: var(--webchat-colorNeutralForegroundOnBrand); + } + + :global(.ac-pushButton.primary:hover) { + background-color: var(--webchat-colorBrandBackgroundHover); + border-color: transparent; + color: var(--webchat-colorNeutralForegroundOnBrand); + } + + :global(.ac-pushButton.primary:active), + :global(.ac-pushButton):global(.primary).adaptive-card-renderer__action--selected { + background-color: var(--webchat-colorBrandBackgroundPressed); + border-color: transparent; + color: var(--webchat-colorNeutralForegroundOnBrand); + } + } + } + + &:not(.part-grouping-decorator--group) { + .bubble__content { + display: flex; + flex-direction: column; + gap: var(--webchat-spacingVerticalS); + } + } + + .bubble .collapsible-content .collapsible-content__content { + margin-block: var(--webchat-spacingVerticalNone); + + .stacked-layout__attachment-list { + margin-block-start: var(--webchat-spacingVerticalS); + } + + .stacked-layout__attachment { + max-width: var(--webchat-part-grouping__bubble--max-width); + } + } + + .stacked-layout__status { + display: none; + } + } + + &.part-grouping-decorator--group { + .collapsible-grouping__content { + border: var(--webchat-strokeWidthThin) solid transparent; + border-radius: var(--webchat-part-grouping__bubble--border-radius); + position: relative; + + @supports (anchor-name: --webchat-flair) { + anchor-name: --webchat-flair; + position: static; + } + } + + &:has(.collapsible-grouping--open) { + .collapsible-grouping__content { + background-color: var(--webchat-part-grouping__bubble--background-color); + border-color: var(--webchat-part-grouping__bubble--border-color); + } + } + + .bubble .bubble__content { + margin-block: calc(var(--webchat-spacingVerticalS) * -1); + padding-block: var(--webchat-spacingVerticalS); + + .stacked-layout__bubble .stacked-layout__title, + .stacked-layout__bubble .collapsible-content__summary { + color: var(--webchat-colorNeutralForeground2); + margin-block: var(--webchat-spacingVerticalXXS); + } + + :global(.stacked-loyout__bubble) .stacked-layout__message-status, + .stacked-layout__bubble .stacked-layout__title, + .stacked-layout__bubble .collapsible-content__summary, + .stacked-layout__bubble .text-content { + font-size: var(--webchat-fontSizeBase200); + line-height: var(--webchat-lineHeightBase200); + } + + .stacked-layout__bubble .text-content { + color: var(--webchat-colorNeutralForeground3); + } + } + } + + &.part-grouping-decorator--from-bot:not(.part-grouping-decorator--group) .bubble__content { + box-sizing: content-box; + padding-block: var(--webchat-spacingVerticalM); + padding-inline: var(--webchat-spacingHorizontalL); + } + + &:not(.part-grouping-decorator--group) .stacked-layout .bubble .bubble__content { + border-width: var(--webchat-strokeWidthThin); + border-style: solid; + } + + &:not(.part-grouping-decorator--group) .bubble:not(.bubble--from-user) .bubble__content { + anchor-name: --webchat-flair; + max-width: unset; + } + + .activity-status { + margin: 0 0 var(--webchat-spacingHorizontalXXS); + } + + .stacked-layout__status { + order: -1; + } + + &:has(.bubble--from-user) { + .bubble { + margin-block-end: var(--webchat-spacingVerticalM); + } + } + + &:has(.bubble.bubble--from-user), + &:has(:global(.pre-chat-message-activity)), + &:has(:global(.liner-message-activity)) { + padding-inline-start: var(--webchat-spacingHorizontalNone); + } + + .bubble .text-content .text-content__generated-badge { + display: none; + } + + .stacked-layout { + margin-inline: var(--webchat-spacingHorizontalSNudge); + } + + .stacked-layout:has(.collapsible-grouping) { + margin-inline: var(--webchat-spacingHorizontalNone); + } + + .collapsible-grouping { + margin-inline: var(--webchat-spacingHorizontalXS); + } + + .collapsible-grouping .collapsible-grouping__chevron, + .collapsible-content .collapsible-content__chevron { + transform: rotate(270deg); + } + + .collapsible-grouping--open .collapsible-grouping__chevron, + .collapsible-content[open] .collapsible-content__chevron { + transform: rotate(360deg); + } + + .collapsible-grouping__header { + margin-inline: calc(var(--webchat-spacingHorizontalXS) * -1); + } + + .collapsible-grouping__title { + color: var(--webchat-colorNeutralForeground2); + font-size: var(--webchat-fontSizeBase200); + font-weight: var(--webchat-fontWeightSemibold); + line-height: var(--webchat-lineHeightBase200); + } + + .collapsible-grouping__content { + anchor-name: --webchat-flair; + background: var(--webchat-colorNeutralBackground1); + background-clip: padding-box; + border-radius: var(--webchat-borderRadius2XLarge); + height: unset; + opacity: unset; + visibility: unset; + } + + .collapsible-grouping__list > * { + interpolate-size: allow-keywords; + transition: + margin calc(var(--webchat__transition-duration) / 2) var(--webchat__transition-easing), + height calc(var(--webchat__transition-duration) / 2) var(--webchat__transition-easing); + } + + .collapsible-grouping__content--open .collapsible-grouping__list > * { + /* We use animation so we can reset needed properties immediately upon switching classes */ + animation: collapsible-grouping__fade-in var(--webchat__transition-duration) + calc(var(--webchat__transition-duration) / 4) var(--webchat__transition-easing) both; + height: auto; + transition: + margin var(--webchat__transition-duration) var(--webchat__transition-easing), + height var(--webchat__transition-duration) var(--webchat__transition-easing); + } + + .collapsible-grouping__content:not(.collapsible-grouping__content--open) { + /* We use animation so we can reset needed properties immediately upon switching classes */ + animation: collapsible-grouping__fade-in var(--webchat__transition-duration) var(--webchat__transition-duration) + var(--webchat__transition-easing) both; + } + + .collapsible-grouping__content:not(.collapsible-grouping__content--open) .collapsible-grouping__list { + padding: var(--webchat-spacingVerticalNone) var(--webchat-spacingHorizontalNone); + + > *:not(:has(.stacked-layout__message-status--incomplete)) { + height: 0; + margin: 0; + visibility: hidden; + overflow: clip; + } + + > *:has(.stacked-layout__message-status--incomplete) { + /* We use animation so we can reset needed properties immediately upon switching classes */ + animation: collapsible-grouping__fade-in var(--webchat__transition-duration) var(--webchat__transition-duration) + var(--webchat__transition-easing) both; + pointer-events: none; + + .stacked-layout__message-status--incomplete, + .collapsible-content__chevron { + visibility: hidden; + } + } + + &:has(.stacked-layout__message-status--incomplete) { + padding: var(--webchat-spacingVerticalNone) var(--webchat-spacingHorizontalNone); + transform: translate(-4px, -20px); + margin-block-end: calc(-20px - var(--webchat--collapsible-grouping__list--gap)); + } + } + + .stacked-layout .stacked-layout__message-status { + --webchat__component-icon--size: 14px; + + color: var(--webchat-colorNeutralForeground2); + height: 20px; + margin-inline: var(--webchat-spacingHorizontalNone) var(--webchat-spacingHorizontalS); + width: 20px; + + .component-icon { + height: unset; + width: unset; + } + } + + .stacked-layout .transcript-focus-area__activity-list { + --webchat--collapsible-grouping__list--gap: var(--webchat-spacingVerticalS); + padding: var(--webchat-spacingVerticalM) var(--webchat-spacingHorizontalM); + } + + .border-flair { + border-radius: var(--webchat-borderRadius2XLarge); + inset: anchor(top) anchor(right) anchor(bottom) anchor(left); + position-anchor: --webchat-flair; + } + } + /* #endregion */ +} + +/* #endregion */ + +/* #region SendBox */ + +:global(.webchat-fluent) .sendbox__attachment-bar.send-box-attachment-bar { + .send-box-attachment-bar__box { + gap: var(--webchat-spacingVerticalXS) var(--webchat-spacingHorizontalXS); + } + + &.send-box-attachment-bar--as-list-item { + margin-inline-end: calc(-1 * var(--webchat-spacingHorizontalS)); + } + + &.send-box-attachment-bar--as-list-item .send-box-attachment-bar__box:not(:empty) { + padding: var(--webchat-spacingVerticalXS) var(--webchat-spacingHorizontalXS); + } + + .send-box-attachment-bar-item.send-box-attachment-bar-item--as-list-item { + box-shadow: var(--webchat-shadow2); + border-radius: var(--webchat-borderRadiusMedium); + padding: var(--webchat-spacingVerticalXXS) var(--webchat-spacingHorizontalXXS); + } + + .send-box-attachment-bar-item.send-box-attachment-bar-item--as-thumbnail { + border: var(--webchat-strokeWidthThin) solid var(--webchat-colorNeutralStroke1Selected); + border-radius: var(--webchat-borderRadiusXLarge); + } + + .send-box-attachment-bar-item .send-box-attachment-bar-item__delete-button { + background-color: var(--webchat-colorNeutralBackground1); + border-radius: var(--webchat-borderRadiusMedium); + color: var(--webchat-colorNeutralForeground1); + transition: + background-color var(--webchat-durationNormal) var(--webchat-curveAccelerateMid), + color var(--webchat-durationNormal) var(--webchat-curveAccelerateMid), + opacity var(--webchat-durationUltraFast) var(--webchat-curveAccelerateMid); + + &:hover { + background-color: var(--webchat-colorNeutralBackground1Hover); + color: var(--webchat-colorNeutralForeground1Hover); + } + + &:active { + background-color: var(--webchat-colorNeutralBackground1Pressed); + color: var(--webchat-colorNeutralForeground1Pressed); + } + + &:disabled, + &[aria-disabled='true'] { + background-color: var(--webchat-colorNeutralBackgroundDisabled); + border-color: var(--webchat-colorNeutralStrokeDisabled); + color: var(--webchat-colorNeutralForegroundDisabled); + } + } + + .send-box-attachment-bar-item.send-box-attachment-bar-item.send-box-attachment-bar-item.send-box-attachment-bar-item--as-list-item + .send-box-attachment-bar-item__preview { + padding-inline: var(--webchat-spacingHorizontalS); + } + + .send-box-attachment-bar-item-file-preview.send-box-attachment-bar-item-file-preview--as-list-item { + gap: var(--webchat-spacingVerticalS) var(--webchat-spacingHorizontalS); + } +} + +/* #endregion */ + +/* #region Theme */ + +/* Scrollbars */ +:global(.webchat-fluent).theme .code-block-content .code-block-content__code-block, +:global(.webchat-fluent).theme .send-box-attachment-bar.send-box-attachment-bar--as-list-item, +:global(.webchat-fluent).theme .text-area.text-area--scroll, +:global(.webchat-fluent).theme :global(.webchat__basic-transcript .webchat__basic-transcript__scrollable), +:global(.webchat-fluent).theme .render-markdown [data-math-type='block'], +:global(.webchat-fluent).theme .view-code-dialog__body, +:global(.webchat-fluent).theme :global(.view-code-dialog__code-body) { + /* Edge uses -webkit-scrollbar if scrollbar-* is not set */ + scrollbar-color: unset; + scrollbar-width: unset; + /* Firefox */ + -moz-scrollbar-color: var(--webchat-colorNeutralForeground5) transparent; + -moz-scrollbar-width: thin; + + &::-webkit-scrollbar { + height: var(--webchat-spacingVerticalMNudge); + width: var(--webchat-spacingVerticalMNudge); + visibility: hidden; + } + + &::-webkit-scrollbar-corner { + background: transparent; + } + + &:hover::-webkit-scrollbar { + height: var(--webchat-spacingVerticalMNudge); + width: var(--webchat-spacingVerticalMNudge); + } + + &::-webkit-scrollbar-thumb { + background-color: transparent; + } + + &:hover::-webkit-scrollbar-thumb, + &:has(> .text-area-input:focus)::-webkit-scrollbar-thumb { + background-clip: padding-box; + background-color: var(--webchat-colorNeutralForeground5); + border-radius: 10px; + border-right: 4px solid transparent; + border: 3px solid transparent; + } + + &:hover::-webkit-scrollbar-thumb:hover { + background-clip: padding-box; + background-color: var(--webchat-colorNeutralForeground5); + border-radius: 10px; + border-right: 2px solid transparent; + border: 1px solid transparent; + } +} + +:global(.webchat-fluent).theme.variant-copilot .transcript-focus-area { + --webchat__transcript--spacing: var(--webchat-spacingVerticalM); +} + +/* Transcript focus indicator color */ +:global(.webchat-fluent).theme .transcript-focus-area__transcript-indicator { + --webchat__color--transcript-visual-keyboard-indicator: var(--webchat-colorStrokeFocus2); +} + +/* Transcript Content indicator without focus-visible state */ +:global(.webchat-fluent).theme .transcript-focus-area:not(:focus-visible) .transcript-focus-area__indicator { + /* Hide activity focus when no focus-visible state */ + display: none; +} + +/* Transcript Focus indicator when content focused */ +:global(.webchat-fluent).theme .transcript-focus-area:focus-visible:has(.transcript-focus-area__content--focused) { + .transcript-focus-area__transcript-indicator { + /* Hide transcript focus when an activity is focused */ + display: none; + } +} + +/* Transcript focused content */ +:global(.webchat-fluent).theme .transcript-focus-area:focus-visible .transcript-focus-area__content--focused { + .transcript-focus-area__indicator { + /* Hide transcript activity focus when the content is focused */ + display: none; + } + + /* Transcript activity focused directly */ + > :global(.webchat__focus-trap) > :global(.webchat__basic-transcript__activity-body) .bubble { + display: grid; + grid-template-areas: 'focused-content'; + position: static; + + .bubble__content { + grid-area: focused-content; + } + + &::after { + border-radius: var(--webchat-part-grouping__bubble--border-radius); + content: ''; + grid-area: focused-content; + outline: var(--webchat-strokeWidthThick) solid var(--webchat-colorStrokeFocus2); + pointer-events: none; + } + } + + &:has(.collapsible-grouping) > .transcript-focus-area__content-overlay .transcript-focus-area__indicator { + display: none; + } + + .collapsible-grouping::after { + border-radius: var(--webchat-part-grouping__bubble--border-radius); + border: none; + content: ''; + inset: 0; + isolation: isolate; + outline: var(--webchat-strokeWidthThick) solid var(--webchat-colorStrokeFocus2); + pointer-events: none; + position: absolute; + } + + :global(.activity-decorator) .bubble .bubble__nub-pad { + display: none; + } + + :global(.pre-chat-message-activity)::after { + border-radius: var(--webchat-part-grouping__bubble--border-radius); + content: ''; + inset: 0; + outline-offset: 0; + outline: var(--webchat-strokeWidthThick) solid var(--webchat-colorStrokeFocus2); + pointer-events: none; + position: absolute; + } + + :global(.pre-chat-message-activity)::after { + border-radius: var(--webchat-borderRadiusMedium); + outline-offset: -3px; + } + + :global(.liner-message-activity__text) { + border-radius: 20px; + outline-offset: 4px; + outline: var(--webchat-strokeWidthThick) solid var(--webchat-colorStrokeFocus2); + } +} + +/* Transcript focused content copilot variant */ +:global(.webchat-fluent).theme.variant-copilot + .transcript-focus-area:focus-visible + .transcript-focus-area__content--focused { + .collapsible-grouping::after { + inset: -4px; + } +} + +/* Code block */ +:global(.webchat-fluent).theme .code-block { + border: none; + font-family: var(--webchat-fontFamilyMonospace); + font-size: var(--webchat-fontSizeBase300); + + --webchat__code-block__copy-button--color: var(--webchat-colorNeutralForeground1); + --webchat__code-block__copy-button--background: var(--webchat-colorNeutralBackground3); + + .code-block-copy-button { + background: transparent; + border: none; + color: var(--webchat__code-block__copy-button--color); + height: 20px; + margin-inline-start: var(--webchat-spacingHorizontalS); + padding: 0; + width: 20px; + transition: background-color var(--webchat-durationNormal) var(--webchat-curveDecelerateMid); + + /* Without a border, the button has no visible boundary in forced colors, so draw one as the default theme does. */ + @media (forced-colors: active) { + border: var(--webchat-strokeWidthThin) solid ButtonText; + } + + .code-block-copy-button__icon { + --webchat__component-icon--color: currentColor; + + @media (forced-colors: active) { + --webchat__component-icon--color: ButtonText; + } + } + + &:hover { + background: var(--webchat__code-block__copy-button--background); + color: var(--webchat__code-block__copy-button--color); + } + + &:active { + background: var(--webchat__code-block__copy-button--background); + color: var(--webchat__code-block__copy-button--color); + } + + &:focus { + color: var(--webchat__code-block__copy-button--color); + outline-color: currentColor; + } + } + + &:has(.code-block__body:focus-visible):focus-within { + outline: var(--webchat-strokeWidthThick) solid var(--webchat-colorStrokeFocus2); + outline-offset: calc(var(--webchat-strokeWidthThick) * -1); + + .code-block__body { + outline: none; + } + } + + .code-block__body { + /* TODO: what is the right value? The value below matches "normal" for contents display */ + line-height: 1.435; + } + + &:has(> .code-block__theme--github-dark-default) { + --webchat__background--code-block: var(--codeBlockBackground, var(--webchat-colorGrey8)); + --webchat__code-block__copy-button--background: var( + --codeBlockCopyButtonBackgroundPressed, + var(--webchat-colorGrey14) + ); + --webchat__code-block__copy-button--color: var(--codeBlockCopyButtonForeground, var(--webchat__color--code-block)); + --webchat__color--code-block: var(--codeBlockForeground, var(--webchat-colorGrey98)); + } + + &:has(> .code-block__theme--github-light-default) { + --webchat__background--code-block: var(--codeBlockBackground, var(--webchat-colorGrey98)); + --webchat__code-block__copy-button--background: var(--codeBlockBackgroundPressed, var(--webchat-colorGrey92)); + --webchat__code-block__copy-button--color: var(--codeBlockCopyButtonForeground, var(--webchat__color--code-block)); + --webchat__color--code-block: var(--codeBlockForeground, var(--webchat-colorGrey8)); + } +} + +/* View Code dialog */ +:global(.webchat-fluent).theme :global(.webchat__modal-dialog).view-code-dialog { + :global(.webchat__modal-dialog__close-button-layout) { + display: flex; + padding: var(--webchat-spacingVerticalS) var(--webchat-spacingHorizontalS); + } + + :global(.webchat__modal-dialog__close-button) { + align-items: center; + aspect-ratio: 1; + display: flex; + height: var(--webchat-lineHeightBase300); + justify-content: center; + padding: 0; + width: unset; + } + + .code-block-copy-button { + --webchat__code-block__copy-button--color: var(--webchat-colorNeutralForeground1); + --webchat__code-block__copy-button--background: var(--webchat-colorNeutralBackground3); + + margin-block-start: var(--webchat-spacingVerticalS); + position: absolute; + right: 32px; + top: 0; + } + + :global(.webchat__modal-dialog__box) { + width: fit-content; + } + + :global(.webchat__modal-dialog__body) { + margin: 0; + padding: var(--webchat-spacingVerticalS) var(--webchat-spacingHorizontalM); + } + + .view-code-dialog__title { + font-size: var(--webchat-fontSizeBase300); + font-weight: var(--webchat-fontWeightSemibold); + line-height: var(--webchat-lineHeightBase300); + } + + .view-code-dialog__body { + font-family: var(--webchat-fontFamilyMonospace); + font-size: var(--webchat-fontSizeBase300); + margin: 0 calc(var(--webchat-spacingHorizontalM) * -1); + padding: 0 var(--webchat-spacingHorizontalM); + } + + .code-block__body { + /* TODO: what is the right value? The value below matches "normal" for block display */ + line-height: normal; + } + + .view-code-dialog__footer { + color: var(--webchat-colorNeutralForeground4); + font-size: var(--webchat-fontSizeBase100); + line-height: var(--webchat-lineHeightBase100); + } + + :global(.view-code-dialog__link) { + color: var(--webchat-colorBrandForegroundLink); + text-decoration-color: transparent; + + &:target { + color: var(--webchat-colorBrandForegroundLinkSelected); + } + &:hover { + color: var(--webchat-colorBrandForegroundLinkHover); + text-decoration: underline 1px currentColor; + } + &:active { + color: var(--webchat-colorBrandForegroundLinkPressed); + } + &:focus-visible { + outline: none; + text-decoration: underline 1px double var(--webchat-colorStrokeFocus2); + } + } +} + +/* Activity button */ +:global(.webchat-fluent).theme .activity-button { + background: var(--webchat-colorNeutralBackground1); + border-radius: var(--webchat-borderRadiusMedium); + border: var(--webchat-strokeWidthThin) solid var(--webchat-colorNeutralStroke1); + color: var(--webchat-colorNeutralForeground1); + gap: var(--webchat-spacingHorizontalXS); + padding: 5px var(--webchat-spacingHorizontalM); + + &:hover { + background: var(--webchat-colorNeutralBackground1Hover); + border: var(--webchat-strokeWidthThin) solid var(--webchat-colorNeutralStroke1Hover); + color: var(--webchat-colorNeutralForeground1Hover); + } + + &:active { + background: var(--webchat-colorNeutralBackground1Pressed); + border: var(--webchat-strokeWidthThin) solid var(--webchat-colorNeutralStroke1Pressed); + color: var(--webchat-colorNeutralForeground1Pressed); + } + + &:focus-visible { + background: var(--webchat-colorNeutralBackground1); + outline: var(--webchat-strokeWidthThick) solid var(--webchat-colorStrokeFocus2); + outline-offset: calc(var(--webchat-strokeWidthThick) * -1); + } + + &[aria-disabled='true'] { + background: var(--webchat-colorNeutralBackgroundDisabled); + border: var(--webchat-strokeWidthThin) solid var(--webchat-colorNeutralStrokeDisabled); + color: var(--webchat-colorNeutralForegroundDisabled); + } + + .activity-button__icon { + height: 20px; + width: 20px; + + --webchat__component-icon--color: var(--webchat-colorNeutralForeground2); + + @media (forced-colors: active) { + --webchat__component-icon--color: ButtonText; + } + } +} + +/* Pause button over animated images: a band in the button background around the focus indicator keeps it visible over any image */ +:global(.webchat-fluent).theme .activity-button.animated-image-content__button:focus-visible::after { + outline: var(--webchat-strokeWidthThick) solid var(--webchat-colorNeutralBackground1); + + @media (forced-colors: active) { + outline-color: ButtonFace; + } +} + +/* Feedback button */ +:global(.webchat-fluent).theme .thumb-button, +:global(.webchat-fluent).theme .thumb-button.thumb-button--large { + .thumb-button__image { + color: var(--webchat-colorNeutralForeground1); + } + + &:has(.thumb-button__input:focus-visible) { + outline: var(--webchat-strokeWidthThick) solid var(--webchat-colorStrokeFocus2); + } + + &:has(.thumb-button__input[aria-disabled='true']) .thumb-button__image { + color: var(--webchat-colorNeutralForegroundDisabled); + } +} + +/* Feedback Form */ +:global(.webchat-fluent).theme .feedback-form { + .feedback-form__form-header { + color: var(--webchat-colorNeutralForeground1); + font-size: var(--webchat-fontSizeBase300); + font-weight: var(--webchat-fontWeightRegular); + line-height: var(--webchat-lineHeightBase300); + } + + .feedback-form__form { + gap: var(--webchat-spacingVerticalXS); + } + + .feedback-form__form-footer { + color: var(--webchat-colorNeutralForeground1); + font-size: var(--webchat-fontSizeBase100); + font-weight: var(--webchat-fontWeightRegular); + line-height: var(--webchat-lineHeightBase200); + } + + .feedback-form__submit-button { + background-color: var(--webchat-colorBrandBackground); + border: var(--webchat-strokeWidthThin) solid var(--webchat-colorBrandBackground); + border-radius: var(--webchat-borderRadiusMedium); + color: var(--webchat-colorNeutralForegroundOnBrand); + cursor: pointer; + font-size: var(--webchat-fontSizeBase300); + padding: var(--webchat-spacingVerticalNone) var(--webchat-spacingHorizontalS); + transition: background-color var(--webchat-durationNormal) var(--webchat-curveDecelerateMid); + + &:hover { + background-color: var(--webchat-colorBrandBackgroundHover); + border: var(--webchat-strokeWidthThin) solid var(--webchat-colorBrandBackgroundHover); + } + + &:focus-visible { + outline: var(--webchat-strokeWidthThick) solid var(--webchat-colorStrokeFocus2); + outline-offset: calc(var(--webchat-strokeWidthThin) * -1); + } + + &:active { + background-color: var(--webchat-colorBrandBackgroundPressed); + border: var(--webchat-strokeWidthThin) solid var(--webchat-colorBrandBackgroundPressed); + } + } + + .feedback-form__cancel-button { + background-color: var(--webchat-colorNeutralBackground1); + border: var(--webchat-strokeWidthThin) solid var(--webchat-colorNeutralStroke1); + border-radius: var(--webchat-borderRadiusMedium); + color: var(--webchat-colorNeutralForeground1); + cursor: pointer; + font-size: var(--webchat-fontSizeBase300); + padding: var(--webchat-spacingVerticalNone) var(--webchat-spacingHorizontalS); + transition: background-color var(--webchat-durationNormal) var(--webchat-curveDecelerateMid); + + &:hover { + background-color: var(--webchat-colorNeutralBackground1Hover); + border: var(--webchat-strokeWidthThin) solid var(--webchat-colorNeutralStroke1Hover); + color: currentColor; + } + + &:focus-visible { + outline: var(--webchat-strokeWidthThick) solid var(--webchat-colorStrokeFocus2); + outline-offset: calc(var(--webchat-strokeWidthThin) * -1); + } + + &:active { + background-color: var(--webchat-colorNeutralBackground1Pressed); + border: var(--webchat-strokeWidthThin) solid var(--webchat-colorNeutralStroke1Pressed); + } + } + + .feedback-form__text-box { + background-color: var(--webchat-colorNeutralBackground1); + border-radius: var(--webchat-borderRadiusMedium); + border: var(--webchat-strokeWidthThin) solid var(--webchat-colorNeutralStroke1); + font-size: var(--webchat-fontSizeBase300); + line-height: var(--webchat-lineHeightBase300); + gap: var(--webchat-spacingVerticalSNudge); + padding: var(--webchat-spacingVerticalS) var(--webchat-spacingHorizontalM); + + &::after { + border-bottom-left-radius: var(--webchat-borderRadiusMedium); + border-bottom-right-radius: var(--webchat-borderRadiusMedium); + border-bottom: var(--webchat-strokeWidthThicker) solid var(--webchat-colorBrandForeground1); + clip-path: inset(calc(100% - var(--webchat-strokeWidthThicker)) 50% 0 50%); + } + &:focus-within { + border: var(--webchat-strokeWidthThin) solid var(--webchat-colorNeutralStroke1); + } + &:focus-within::after { + clip-path: inset(calc(100% - var(--webchat-strokeWidthThicker)) 0 0 0); + transition: clip-path var(--webchat-durationNormal) var(--webchat-curveDecelerateMid); + } + } +} + +/* #endregion */ diff --git a/packages/fluent-theme/src/private/fluentCSSModuleTheme.spec.ts b/packages/fluent-theme/src/private/fluentCSSModuleTheme.spec.ts index f51acf45cd..b29e80e8ad 100644 --- a/packages/fluent-theme/src/private/fluentCSSModuleTheme.spec.ts +++ b/packages/fluent-theme/src/private/fluentCSSModuleTheme.spec.ts @@ -4,7 +4,7 @@ import * as JestGlobals from '@jest/globals'; import { scenario } from '@testduet/given-when-then'; import { readdirSync, readFileSync } from 'fs'; import { transform } from 'lightningcss'; -import { basename, join, relative, sep } from 'path'; +import { join, relative } from 'path'; const PACKAGES_PATH = join(__dirname, '../../..'); @@ -53,20 +53,47 @@ function getGlobalClassNames(path: string): readonly string[] { const webChatLocalNames = new Set([...listCSSModules('component'), ...listCSSModules('bundle')].flatMap(getLocalNames)); -const webChatCSSModuleNames = new Set( - [...listCSSModules('component'), ...listCSSModules('bundle')].map(path => basename(path)) -); - -const isOverrideModule = (path: string): boolean => path.includes(`${sep}overrides${sep}`); - -const fluentLocalNames = new Set( - listCSSModules('fluent-theme') - .filter(path => !isOverrideModule(path)) - .flatMap(getLocalNames) -); +// The CSS module with every Fluent rule for Web Chat elements, which FluentThemeProvider passes whole. +const OVERRIDE_MODULE_PATH = join(__dirname, 'fluentCSSModuleTheme.module.css'); + +const isOverrideModule = (path: string): boolean => path === OVERRIDE_MODULE_PATH; + +const fluentCSSModuleLocalNames: readonly ReadonlySet[] = listCSSModules('fluent-theme') + .filter(path => !isOverrideModule(path)) + .map(path => new Set(getLocalNames(path))); + +const fluentLocalNames = new Set(fluentCSSModuleLocalNames.flatMap(localNames => [...localNames])); + +// Fluent's own local names of each compound selector (e.g. ".theme.variant-copilot:hover"), with nesting resolved. +function getFluentCompounds(path: string): readonly (readonly string[])[] { + let css = transform({ + // eslint-disable-next-line security/detect-non-literal-fs-filename + code: readFileSync(path), + // Marks local names, so readable class names from :global() are left out. + cssModules: { animation: false, customIdents: false, grid: false, pattern: 'local--[local]' }, + filename: path, + // Old enough to resolve nesting. + targets: { chrome: 90 << 16 } + }).code.toString(); + + // Drops arguments of :has(), :not() and others, whose compounds are not on the same element. + for (let previous; previous !== css; ) { + previous = css; + css = css.replace(/\([^()]*\)/gu, ''); + } -// eslint-disable-next-line security/detect-non-literal-fs-filename -const FLUENT_CSS_MODULE_THEME_SOURCE = readFileSync(join(__dirname, 'fluentCSSModuleTheme.ts'), 'utf8'); + return Object.freeze( + // Compounds are runs of class names and pseudo-classes, so anything else separates them. + css + .split(/[^-\w.:]/u) + .map(compound => + Array.from(compound.matchAll(/\.local--([-\w]+)/gu)).flatMap(([, localName]) => + localName && !webChatLocalNames.has(localName) ? [localName] : [] + ) + ) + .filter(compound => compound.length) + ); +} // FluentIcon's own classes, which happen to share a name with a Web Chat class. const FLUENT_ICON_LOCAL_NAMES: ReadonlySet = Object.freeze( @@ -82,7 +109,7 @@ scenario( paths.map(path => ({ path: relative(PACKAGES_PATH, path), webChatGlobalClassNames: getGlobalClassNames(path).filter(className => webChatLocalNames.has(className)), - // A Web Chat local name in a Fluent rule only matches from an override module, which is passed whole. + // A Web Chat local name in a Fluent rule only matches from the override module, which is passed whole. webChatLocalNamesOutsideOverrides: isOverrideModule(path) ? [] : getLocalNames(path).filter( @@ -93,37 +120,34 @@ scenario( .then('none should select a local name of a Web Chat CSS module through :global()', (_, results) => expect(results.filter(({ webChatGlobalClassNames }) => webChatGlobalClassNames.length)).toEqual([]) ) - .and('only override modules should use local names of Web Chat CSS modules', (_, results) => + .and('only the override module should use local names of Web Chat CSS modules', (_, results) => expect( results.filter(({ webChatLocalNamesOutsideOverrides }) => webChatLocalNamesOutsideOverrides.length) ).toEqual([]) ); bdd - .given('every Fluent override module', () => listCSSModules('fluent-theme').filter(isOverrideModule)) - .when('reading its local names', paths => - paths.map(path => ({ - // Besides Web Chat local names, only Fluent's own local names that scope the rules, e.g. ".theme". - // The composer adds their class names to Fluent's elements as well. - localNamesNotFromWebChatNorFluent: getLocalNames(path).filter( - localName => !webChatLocalNames.has(localName) && !fluentLocalNames.has(localName) - ), - name: basename(path), - path: relative(PACKAGES_PATH, path) - })) - ) - .then('should only use local names of Web Chat and of Fluent CSS modules', (_, results) => - expect( - results.filter(({ localNamesNotFromWebChatNorFluent }) => localNamesNotFromWebChatNorFluent.length) - ).toEqual([]) - ) - .and('should mirror a Web Chat CSS module', (_, results) => - expect(results.filter(({ name }) => !webChatCSSModuleNames.has(name))).toEqual([]) + .given('the Fluent override module', () => OVERRIDE_MODULE_PATH) + .when('reading its local names', path => ({ + // Besides Web Chat local names, only Fluent's own local names that scope the rules, e.g. ".theme". + // The composer adds their class names to Fluent's elements as well. + localNamesNotFromWebChatNorFluent: getLocalNames(path).filter( + localName => !webChatLocalNames.has(localName) && !fluentLocalNames.has(localName) + ), + // A Fluent element only gets the override module's class names for the local names of its own CSS module, + // e.g. ".theme.variant-copilot" needs a Fluent CSS module that declares both "theme" and "variant-copilot". + undeclaredFluentCompounds: getFluentCompounds(path).filter( + compound => + !fluentCSSModuleLocalNames.some(localNames => compound.every(localName => localNames.has(localName))) + ) + })) + .then( + 'should only use local names of Web Chat and of Fluent CSS modules', + (_, { localNamesNotFromWebChatNorFluent }) => expect(localNamesNotFromWebChatNorFluent).toEqual([]) ) - .and('should be passed to the CSS module theme', (_, results) => - expect( - results.filter(({ name }) => !FLUENT_CSS_MODULE_THEME_SOURCE.includes(`'../overrides/${name}'`)) - ).toEqual([]) + .and( + "should only combine Fluent's local names that one Fluent CSS module declares", + (_, { undeclaredFluentCompounds }) => expect(undeclaredFluentCompounds).toEqual([]) ); }, JestGlobals diff --git a/packages/fluent-theme/src/private/fluentCSSModuleTheme.ts b/packages/fluent-theme/src/private/fluentCSSModuleTheme.ts index 923590c9b8..39e10526dc 100644 --- a/packages/fluent-theme/src/private/fluentCSSModuleTheme.ts +++ b/packages/fluent-theme/src/private/fluentCSSModuleTheme.ts @@ -1,62 +1,11 @@ import { combineCSSModules } from 'botframework-webchat/internal.js'; -import textContentOverrideStyles from '../overrides/TextContent.module.css'; -import messageStatusLoaderOverrideStyles from '../overrides/MessageStatusLoader.module.css'; -import bubbleOverrideStyles from '../overrides/Bubble.module.css'; -import renderMarkdownOverrideStyles from '../overrides/RenderMarkdown.module.css'; -import linkDefinitionsOverrideStyles from '../overrides/LinkDefinitions.module.css'; -import activityButtonOverrideStyles from '../overrides/ActivityButton.module.css'; -import collapsibleGroupingOverrideStyles from '../overrides/CollapsibleGrouping.module.css'; -import partGroupingActivityOverrideStyles from '../overrides/PartGroupingActivity.module.css'; -import stackedLayoutOverrideStyles from '../overrides/StackedLayout.module.css'; -import componentIconOverrideStyles from '../overrides/ComponentIcon.module.css'; -import carouselFilmStripOverrideStyles from '../overrides/CarouselFilmStrip.module.css'; -import codeBlockContentOverrideStyles from '../overrides/CodeBlockContent.module.css'; -import codeBlockCopyButtonOverrideStyles from '../overrides/CodeBlockCopyButton.module.css'; -import collapsibleContentOverrideStyles from '../overrides/CollapsibleContent.module.css'; -import transcriptFocusOverrideStyles from '../overrides/TranscriptFocus.module.css'; -import borderFlairOverrideStyles from '../overrides/BorderFlair.module.css'; -import adaptiveCardRendererOverrideStyles from '../overrides/AdaptiveCardRenderer.module.css'; -import activityStatusOverrideStyles from '../overrides/ActivityStatus.module.css'; -import attachmentBarOverrideStyles from '../overrides/AttachmentBar.module.css'; -import attachmentBarItemOverrideStyles from '../overrides/AttachmentBarItem.module.css'; -import filePreviewOverrideStyles from '../overrides/FilePreview.module.css'; -import codeBlockOverrideStyles from '../overrides/CodeBlock.module.css'; -import viewCodeDialogOverrideStyles from '../overrides/ViewCodeDialog.module.css'; -import animatedImageContentOverrideStyles from '../overrides/AnimatedImageContent.module.css'; -import thumbButtonOverrideStyles from '../overrides/ThumbButton.module.css'; -import feedbackFormOverrideStyles from '../overrides/FeedbackForm.module.css'; +import overrideStyles from './fluentCSSModuleTheme.module.css'; // Fluent's class names for the Web Chat elements it styles, for in FluentThemeProvider. -// Each override module mirrors one Web Chat CSS module and is passed whole, in the order its rules had in -// Fluent's CSS, to keep the cascade. Built once, so every useStyles() below the composer gets a stable theme. -const fluentCSSModuleTheme = combineCSSModules( - textContentOverrideStyles, - messageStatusLoaderOverrideStyles, - bubbleOverrideStyles, - renderMarkdownOverrideStyles, - linkDefinitionsOverrideStyles, - activityButtonOverrideStyles, - collapsibleGroupingOverrideStyles, - partGroupingActivityOverrideStyles, - stackedLayoutOverrideStyles, - componentIconOverrideStyles, - carouselFilmStripOverrideStyles, - codeBlockContentOverrideStyles, - codeBlockCopyButtonOverrideStyles, - collapsibleContentOverrideStyles, - transcriptFocusOverrideStyles, - borderFlairOverrideStyles, - adaptiveCardRendererOverrideStyles, - activityStatusOverrideStyles, - attachmentBarOverrideStyles, - attachmentBarItemOverrideStyles, - filePreviewOverrideStyles, - codeBlockOverrideStyles, - viewCodeDialogOverrideStyles, - animatedImageContentOverrideStyles, - thumbButtonOverrideStyles, - feedbackFormOverrideStyles -); +// One CSS module holds every Fluent rule for Web Chat elements and is passed whole. Its scope classes (e.g. ".theme") +// also reach Fluent's elements, which declare the same local names in their own CSS modules. +// Built once, so every useStyles() below the composer gets a stable theme. +const fluentCSSModuleTheme = combineCSSModules(overrideStyles); export default fluentCSSModuleTheme;