diff --git a/CHANGELOG.md b/CHANGELOG.md index c34aabfa68..f920c41957 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -299,6 +299,7 @@ Notes: web developers are advised to use [`~` (tilde range)](https://github.com/ - Updated file-based import with `.js` extension and removed the file extension from build config, by [@compulim](https://github.com/compulim), in PR [#5516](https://github.com/microsoft/BotFramework-WebChat/pull/5516) - Fixed [#5518](https://github.com/microsoft/BotFramework-WebChat/issues/5518). Minimal bundled build should work properly, in PR [#5507](https://github.com/microsoft/BotFramework-WebChat/pull/5507), by [@compulim](https://github.com/compulim) - Fixed [#5520](https://github.com/microsoft/BotFramework-WebChat/issues/5520). Version information should be injected when installed via npm, in PR [#5521](https://github.com/microsoft/BotFramework-WebChat/pull/5521), by [@compulim](https://github.com/compulim) +- Fixed placing focus on the code block content, so it is possible to scroll code via keyboard, in PR [#5575](https://github.com/microsoft/BotFramework-WebChat/pull/5575), by [@OEvgeny](https://github.com/OEvgeny) ### Removed diff --git a/__tests__/__image_snapshots__/html/default-adaptive-cards-js-markdown-render-html-when-unset-should-render-sanitized-html-in-adaptive-cards-1-snap.png b/__tests__/__image_snapshots__/html/default-adaptive-cards-js-markdown-render-html-when-unset-should-render-sanitized-html-in-adaptive-cards-1-snap.png index d6b384b57f..8f7580cd9a 100644 Binary files a/__tests__/__image_snapshots__/html/default-adaptive-cards-js-markdown-render-html-when-unset-should-render-sanitized-html-in-adaptive-cards-1-snap.png and b/__tests__/__image_snapshots__/html/default-adaptive-cards-js-markdown-render-html-when-unset-should-render-sanitized-html-in-adaptive-cards-1-snap.png differ diff --git a/__tests__/__image_snapshots__/html/default-message-activity-js-markdown-render-html-when-unset-should-render-sanitized-html-in-message-activity-1-snap.png b/__tests__/__image_snapshots__/html/default-message-activity-js-markdown-render-html-when-unset-should-render-sanitized-html-in-message-activity-1-snap.png index 04d15e4851..dd395c38ac 100644 Binary files a/__tests__/__image_snapshots__/html/default-message-activity-js-markdown-render-html-when-unset-should-render-sanitized-html-in-message-activity-1-snap.png and b/__tests__/__image_snapshots__/html/default-message-activity-js-markdown-render-html-when-unset-should-render-sanitized-html-in-message-activity-1-snap.png differ diff --git a/__tests__/html2/activity/collapsible.copilot.dark.html.snap-4.png b/__tests__/html2/activity/collapsible.copilot.dark.html.snap-4.png index 3f1fed0ab4..912a46ee4c 100644 Binary files a/__tests__/html2/activity/collapsible.copilot.dark.html.snap-4.png and b/__tests__/html2/activity/collapsible.copilot.dark.html.snap-4.png differ diff --git a/__tests__/html2/activity/collapsible.copilot.dark.html.snap-5.png b/__tests__/html2/activity/collapsible.copilot.dark.html.snap-5.png index 5684ddc62f..4ebcd9d163 100644 Binary files a/__tests__/html2/activity/collapsible.copilot.dark.html.snap-5.png and b/__tests__/html2/activity/collapsible.copilot.dark.html.snap-5.png differ diff --git a/__tests__/html2/activity/collapsible.copilot.dark.html.snap-6.png b/__tests__/html2/activity/collapsible.copilot.dark.html.snap-6.png index 984e38de14..bb801cef79 100644 Binary files a/__tests__/html2/activity/collapsible.copilot.dark.html.snap-6.png and b/__tests__/html2/activity/collapsible.copilot.dark.html.snap-6.png differ diff --git a/__tests__/html2/activity/collapsible.copilot.dark.html.snap-7.png b/__tests__/html2/activity/collapsible.copilot.dark.html.snap-7.png index 7590c2cfeb..dc1faed9f3 100644 Binary files a/__tests__/html2/activity/collapsible.copilot.dark.html.snap-7.png and b/__tests__/html2/activity/collapsible.copilot.dark.html.snap-7.png differ diff --git a/__tests__/html2/activity/collapsible.copilot.dark.html.snap-8.png b/__tests__/html2/activity/collapsible.copilot.dark.html.snap-8.png index 539efdf876..1c7027d657 100644 Binary files a/__tests__/html2/activity/collapsible.copilot.dark.html.snap-8.png and b/__tests__/html2/activity/collapsible.copilot.dark.html.snap-8.png differ diff --git a/__tests__/html2/activity/collapsible.copilot.html.snap-4.png b/__tests__/html2/activity/collapsible.copilot.html.snap-4.png index f6fb85a366..5baa45e5d6 100644 Binary files a/__tests__/html2/activity/collapsible.copilot.html.snap-4.png and b/__tests__/html2/activity/collapsible.copilot.html.snap-4.png differ diff --git a/__tests__/html2/activity/collapsible.copilot.html.snap-5.png b/__tests__/html2/activity/collapsible.copilot.html.snap-5.png index d226aad8e5..01b271beb2 100644 Binary files a/__tests__/html2/activity/collapsible.copilot.html.snap-5.png and b/__tests__/html2/activity/collapsible.copilot.html.snap-5.png differ diff --git a/__tests__/html2/activity/collapsible.copilot.html.snap-6.png b/__tests__/html2/activity/collapsible.copilot.html.snap-6.png index ed9cd6444a..c88e1abb70 100644 Binary files a/__tests__/html2/activity/collapsible.copilot.html.snap-6.png and b/__tests__/html2/activity/collapsible.copilot.html.snap-6.png differ diff --git a/__tests__/html2/activity/collapsible.copilot.html.snap-7.png b/__tests__/html2/activity/collapsible.copilot.html.snap-7.png index 9f55894785..3e8ee3b8c5 100644 Binary files a/__tests__/html2/activity/collapsible.copilot.html.snap-7.png and b/__tests__/html2/activity/collapsible.copilot.html.snap-7.png differ diff --git a/__tests__/html2/activity/collapsible.copilot.html.snap-8.png b/__tests__/html2/activity/collapsible.copilot.html.snap-8.png index 0781f6df80..1537d20baf 100644 Binary files a/__tests__/html2/activity/collapsible.copilot.html.snap-8.png and b/__tests__/html2/activity/collapsible.copilot.html.snap-8.png differ diff --git a/__tests__/html2/activity/collapsible.fluent.dark.html.snap-4.png b/__tests__/html2/activity/collapsible.fluent.dark.html.snap-4.png index 900112b8d1..d49471f2ba 100644 Binary files a/__tests__/html2/activity/collapsible.fluent.dark.html.snap-4.png and b/__tests__/html2/activity/collapsible.fluent.dark.html.snap-4.png differ diff --git a/__tests__/html2/activity/collapsible.fluent.dark.html.snap-5.png b/__tests__/html2/activity/collapsible.fluent.dark.html.snap-5.png index 757d823b9d..8da8c331b5 100644 Binary files a/__tests__/html2/activity/collapsible.fluent.dark.html.snap-5.png and b/__tests__/html2/activity/collapsible.fluent.dark.html.snap-5.png differ diff --git a/__tests__/html2/activity/collapsible.fluent.dark.html.snap-6.png b/__tests__/html2/activity/collapsible.fluent.dark.html.snap-6.png index dff4fcfc9b..62722e1377 100644 Binary files a/__tests__/html2/activity/collapsible.fluent.dark.html.snap-6.png and b/__tests__/html2/activity/collapsible.fluent.dark.html.snap-6.png differ diff --git a/__tests__/html2/activity/collapsible.fluent.dark.html.snap-7.png b/__tests__/html2/activity/collapsible.fluent.dark.html.snap-7.png index ebf4e95ca1..f7797243a8 100644 Binary files a/__tests__/html2/activity/collapsible.fluent.dark.html.snap-7.png and b/__tests__/html2/activity/collapsible.fluent.dark.html.snap-7.png differ diff --git a/__tests__/html2/activity/collapsible.fluent.dark.html.snap-8.png b/__tests__/html2/activity/collapsible.fluent.dark.html.snap-8.png index f711dd1f88..eb6ed11d47 100644 Binary files a/__tests__/html2/activity/collapsible.fluent.dark.html.snap-8.png and b/__tests__/html2/activity/collapsible.fluent.dark.html.snap-8.png differ diff --git a/__tests__/html2/activity/collapsible.fluent.html.snap-4.png b/__tests__/html2/activity/collapsible.fluent.html.snap-4.png index 864aa5e9b1..70fdc11b03 100644 Binary files a/__tests__/html2/activity/collapsible.fluent.html.snap-4.png and b/__tests__/html2/activity/collapsible.fluent.html.snap-4.png differ diff --git a/__tests__/html2/activity/collapsible.fluent.html.snap-5.png b/__tests__/html2/activity/collapsible.fluent.html.snap-5.png index d5cc4809cc..70b8eb15ac 100644 Binary files a/__tests__/html2/activity/collapsible.fluent.html.snap-5.png and b/__tests__/html2/activity/collapsible.fluent.html.snap-5.png differ diff --git a/__tests__/html2/activity/collapsible.fluent.html.snap-6.png b/__tests__/html2/activity/collapsible.fluent.html.snap-6.png index e576f463d1..167db55f86 100644 Binary files a/__tests__/html2/activity/collapsible.fluent.html.snap-6.png and b/__tests__/html2/activity/collapsible.fluent.html.snap-6.png differ diff --git a/__tests__/html2/activity/collapsible.fluent.html.snap-7.png b/__tests__/html2/activity/collapsible.fluent.html.snap-7.png index eba3c34ad0..f3bcdacf0a 100644 Binary files a/__tests__/html2/activity/collapsible.fluent.html.snap-7.png and b/__tests__/html2/activity/collapsible.fluent.html.snap-7.png differ diff --git a/__tests__/html2/activity/collapsible.fluent.html.snap-8.png b/__tests__/html2/activity/collapsible.fluent.html.snap-8.png index 9866f1da20..ca4e7cad67 100644 Binary files a/__tests__/html2/activity/collapsible.fluent.html.snap-8.png and b/__tests__/html2/activity/collapsible.fluent.html.snap-8.png differ diff --git a/__tests__/html2/activity/viewCodeButton.scroll.html b/__tests__/html2/activity/viewCodeButton.scroll.html new file mode 100644 index 0000000000..8ee6e6e9e8 --- /dev/null +++ b/__tests__/html2/activity/viewCodeButton.scroll.html @@ -0,0 +1,148 @@ + + + + + + + + + + + + +
+ + + diff --git a/__tests__/html2/activity/viewCodeButton.scroll.html.snap-1.png b/__tests__/html2/activity/viewCodeButton.scroll.html.snap-1.png new file mode 100644 index 0000000000..cfb5065679 Binary files /dev/null and b/__tests__/html2/activity/viewCodeButton.scroll.html.snap-1.png differ diff --git a/__tests__/html2/activity/viewCodeButton.scroll.html.snap-2.png b/__tests__/html2/activity/viewCodeButton.scroll.html.snap-2.png new file mode 100644 index 0000000000..f99463bcac Binary files /dev/null and b/__tests__/html2/activity/viewCodeButton.scroll.html.snap-2.png differ diff --git a/__tests__/html2/activity/viewCodeButton.scroll.html.snap-3.png b/__tests__/html2/activity/viewCodeButton.scroll.html.snap-3.png new file mode 100644 index 0000000000..6334d2c5a5 Binary files /dev/null and b/__tests__/html2/activity/viewCodeButton.scroll.html.snap-3.png differ diff --git a/__tests__/html2/markdown/codeBlockCopyButton/adaptiveCards/behavior.html.snap-1.png b/__tests__/html2/markdown/codeBlockCopyButton/adaptiveCards/behavior.html.snap-1.png index 2e96fa533f..fe060cc77d 100644 Binary files a/__tests__/html2/markdown/codeBlockCopyButton/adaptiveCards/behavior.html.snap-1.png and b/__tests__/html2/markdown/codeBlockCopyButton/adaptiveCards/behavior.html.snap-1.png differ diff --git a/__tests__/html2/markdown/codeBlockCopyButton/adaptiveCards/behavior.html.snap-2.png b/__tests__/html2/markdown/codeBlockCopyButton/adaptiveCards/behavior.html.snap-2.png index cce0c5693d..f4471563c8 100644 Binary files a/__tests__/html2/markdown/codeBlockCopyButton/adaptiveCards/behavior.html.snap-2.png and b/__tests__/html2/markdown/codeBlockCopyButton/adaptiveCards/behavior.html.snap-2.png differ diff --git a/__tests__/html2/markdown/codeBlockCopyButton/adaptiveCards/behavior.html.snap-3.png b/__tests__/html2/markdown/codeBlockCopyButton/adaptiveCards/behavior.html.snap-3.png index 59c8545cd2..2e355fc577 100644 Binary files a/__tests__/html2/markdown/codeBlockCopyButton/adaptiveCards/behavior.html.snap-3.png and b/__tests__/html2/markdown/codeBlockCopyButton/adaptiveCards/behavior.html.snap-3.png differ diff --git a/__tests__/html2/markdown/codeBlockCopyButton/adaptiveCards/layout.html.snap-1.png b/__tests__/html2/markdown/codeBlockCopyButton/adaptiveCards/layout.html.snap-1.png index 12df84876f..234311c24a 100644 Binary files a/__tests__/html2/markdown/codeBlockCopyButton/adaptiveCards/layout.html.snap-1.png and b/__tests__/html2/markdown/codeBlockCopyButton/adaptiveCards/layout.html.snap-1.png differ diff --git a/__tests__/html2/markdown/codeBlockCopyButton/fluent/layout.html.snap-1.png b/__tests__/html2/markdown/codeBlockCopyButton/fluent/layout.html.snap-1.png index 17cdddaeb1..122065c160 100644 Binary files a/__tests__/html2/markdown/codeBlockCopyButton/fluent/layout.html.snap-1.png and b/__tests__/html2/markdown/codeBlockCopyButton/fluent/layout.html.snap-1.png differ diff --git a/__tests__/html2/part-grouping/copilot.dark.html.snap-10.png b/__tests__/html2/part-grouping/copilot.dark.html.snap-10.png index 51b23ba045..99758ef55a 100644 Binary files a/__tests__/html2/part-grouping/copilot.dark.html.snap-10.png and b/__tests__/html2/part-grouping/copilot.dark.html.snap-10.png differ diff --git a/__tests__/html2/part-grouping/copilot.dark.html.snap-8.png b/__tests__/html2/part-grouping/copilot.dark.html.snap-8.png index 137f248704..f2434537a2 100644 Binary files a/__tests__/html2/part-grouping/copilot.dark.html.snap-8.png and b/__tests__/html2/part-grouping/copilot.dark.html.snap-8.png differ diff --git a/__tests__/html2/part-grouping/copilot.dark.html.snap-9.png b/__tests__/html2/part-grouping/copilot.dark.html.snap-9.png index 673377e70f..8167506166 100644 Binary files a/__tests__/html2/part-grouping/copilot.dark.html.snap-9.png and b/__tests__/html2/part-grouping/copilot.dark.html.snap-9.png differ diff --git a/__tests__/html2/part-grouping/copilot.html.snap-10.png b/__tests__/html2/part-grouping/copilot.html.snap-10.png index 6e8d8c9c15..8a170ac928 100644 Binary files a/__tests__/html2/part-grouping/copilot.html.snap-10.png and b/__tests__/html2/part-grouping/copilot.html.snap-10.png differ diff --git a/__tests__/html2/part-grouping/copilot.html.snap-8.png b/__tests__/html2/part-grouping/copilot.html.snap-8.png index fbbbf18026..96bf5a7be0 100644 Binary files a/__tests__/html2/part-grouping/copilot.html.snap-8.png and b/__tests__/html2/part-grouping/copilot.html.snap-8.png differ diff --git a/__tests__/html2/part-grouping/copilot.html.snap-9.png b/__tests__/html2/part-grouping/copilot.html.snap-9.png index 6d6f865ba1..f64fee0f22 100644 Binary files a/__tests__/html2/part-grouping/copilot.html.snap-9.png and b/__tests__/html2/part-grouping/copilot.html.snap-9.png differ diff --git a/__tests__/html2/part-grouping/fluent.dark.html.snap-10.png b/__tests__/html2/part-grouping/fluent.dark.html.snap-10.png index a4f291274f..ef0ff6e4b0 100644 Binary files a/__tests__/html2/part-grouping/fluent.dark.html.snap-10.png and b/__tests__/html2/part-grouping/fluent.dark.html.snap-10.png differ diff --git a/__tests__/html2/part-grouping/fluent.dark.html.snap-8.png b/__tests__/html2/part-grouping/fluent.dark.html.snap-8.png index 6e45fc8b6d..bc29d48361 100644 Binary files a/__tests__/html2/part-grouping/fluent.dark.html.snap-8.png and b/__tests__/html2/part-grouping/fluent.dark.html.snap-8.png differ diff --git a/__tests__/html2/part-grouping/fluent.dark.html.snap-9.png b/__tests__/html2/part-grouping/fluent.dark.html.snap-9.png index d430a74fa5..af63cf6b16 100644 Binary files a/__tests__/html2/part-grouping/fluent.dark.html.snap-9.png and b/__tests__/html2/part-grouping/fluent.dark.html.snap-9.png differ diff --git a/__tests__/html2/part-grouping/fluent.html.snap-10.png b/__tests__/html2/part-grouping/fluent.html.snap-10.png index eeb8ed5acd..0e2e2f2556 100644 Binary files a/__tests__/html2/part-grouping/fluent.html.snap-10.png and b/__tests__/html2/part-grouping/fluent.html.snap-10.png differ diff --git a/__tests__/html2/part-grouping/fluent.html.snap-8.png b/__tests__/html2/part-grouping/fluent.html.snap-8.png index 4f38605605..4ac18f28be 100644 Binary files a/__tests__/html2/part-grouping/fluent.html.snap-8.png and b/__tests__/html2/part-grouping/fluent.html.snap-8.png differ diff --git a/__tests__/html2/part-grouping/fluent.html.snap-9.png b/__tests__/html2/part-grouping/fluent.html.snap-9.png index 01cea55105..b41604d559 100644 Binary files a/__tests__/html2/part-grouping/fluent.html.snap-9.png and b/__tests__/html2/part-grouping/fluent.html.snap-9.png differ diff --git a/packages/component/src/Activity/CodeBlockContent.module.css b/packages/component/src/Activity/CodeBlockContent.module.css index 0bffc60124..eb3da714c3 100644 --- a/packages/component/src/Activity/CodeBlockContent.module.css +++ b/packages/component/src/Activity/CodeBlockContent.module.css @@ -29,7 +29,7 @@ } } -:global(.webchat) .code-block-content__code-block { +:global(.webchat) .code-block-content .code-block-content__code-block { border: none; margin: 0; max-height: 360px; diff --git a/packages/component/src/Attachment/Text/private/ActivityViewCodeButton.tsx b/packages/component/src/Attachment/Text/private/ActivityViewCodeButton.tsx index 9468a266b0..7395db4154 100644 --- a/packages/component/src/Attachment/Text/private/ActivityViewCodeButton.tsx +++ b/packages/component/src/Attachment/Text/private/ActivityViewCodeButton.tsx @@ -1,6 +1,7 @@ import { validateProps } from '@msinternal/botframework-webchat-react-valibot'; +import { useStyles } from '@msinternal/botframework-webchat-styles/react'; import { hooks } from 'botframework-webchat-api'; -import classNames from 'classnames'; +import cx from 'classnames'; import React, { memo, useCallback } from 'react'; import { boolean, object, optional, pipe, readonly, string, type InferInput } from 'valibot'; @@ -10,6 +11,8 @@ import LocalizedString from '../../../Utils/LocalizedString'; import ActivityButton from './ActivityButton'; import CodeContent from './CodeContent'; +import styles from './ViewCodeDialog.module.css'; + const { useLocalizer } = hooks; const activityViewCodeButtonPropsSchema = pipe( @@ -27,8 +30,9 @@ type ActivityViewCodeButtonProps = InferInput { const { className, code, language, title, isAIGenerated } = validateProps(activityViewCodeButtonPropsSchema, props); + const classNames = useStyles(styles); - const [{ activityButton, viewCodeDialog }] = useStyleSet(); + const [{ activityButton }] = useStyleSet(); const showModal = useShowModal(); const localize = useLocalizer(); @@ -37,22 +41,22 @@ const ActivityViewCodeButton = (props: ActivityViewCodeButtonProps) => { () => ( {isAIGenerated && ( -
+
)} ), { - className: classNames('webchat__view-code-dialog', viewCodeDialog), + className: cx('webchat__view-code-dialog', classNames['view-code-dialog']), 'aria-label': localize('ACTIVITY_CODE_ALT', title ?? '') } ); - }, [code, isAIGenerated, language, localize, showModal, title, viewCodeDialog]); + }, [code, isAIGenerated, language, localize, showModal, title, classNames]); return ( -
-

{title}

+
+

{title}

- + {code} {children} diff --git a/packages/component/src/Attachment/Text/private/ViewCodeDialog.module.css b/packages/component/src/Attachment/Text/private/ViewCodeDialog.module.css new file mode 100644 index 0000000000..7ce7ccc673 --- /dev/null +++ b/packages/component/src/Attachment/Text/private/ViewCodeDialog.module.css @@ -0,0 +1,60 @@ +:global(.webchat) .view-code-dialog { + box-sizing: border-box; + display: grid; + height: 100%; + max-height: 100vh; + overflow: hidden; + padding: 1rem; + place-items: center; +} + +:global(.webchat) .view-code-dialog :global(.webchat__modal-dialog__box) { + display: flex; + flex-direction: column; + max-height: 100%; + max-width: 100%; + position: relative; +} + +:global(.webchat) .view-code-dialog :global(.webchat__modal-dialog__close-button-layout) { + position: absolute; + right: 0; +} + +:global(.webchat) .view-code-dialog :global(.webchat__modal-dialog__body) { + display: flex; + flex-direction: column; + gap: var(--webchat__padding--regular); + overflow: hidden; +} + +:global(.webchat) .view-code-dialog__header { + align-items: center; + display: flex; + padding-inline-end: 30px; +} + +:global(.webchat) .view-code-dialog__title { + margin: 0 auto 0 0; + text-transform: capitalize; +} + +:global(.webchat) .view-code-dialog .view-code-dialog__body { + border: none; + border-radius: 0; + overflow: auto; + height: 100%; +} + +:global(.webchat) .view-code-dialog :global(.code-block__body) { + display: block; + /* TODO: align with the rest of code blocks */ + line-height: normal; + padding: 16px 0; +} + +:global(.webchat) .view-code-dialog__footer { + color: var(--webchat__color--subtle); + line-height: 20px; +} + diff --git a/packages/component/src/Styles/createStyleSet.ts b/packages/component/src/Styles/createStyleSet.ts index dabb86f5e1..78744e83c4 100644 --- a/packages/component/src/Styles/createStyleSet.ts +++ b/packages/component/src/Styles/createStyleSet.ts @@ -14,8 +14,6 @@ import createCarouselFilmStripAttachment from './StyleSet/CarouselFilmStripAttac import createCarouselFlipper from './StyleSet/CarouselFlipper'; import createChatHistoryBoxStyleSet from './StyleSet/ChatHistoryBox'; import createCitationModalDialogStyle from './StyleSet/CitationModalDialog'; -import createCodeBlockStyle from './StyleSet/CodeBlock'; -import createCodeBlockCopyButtonStyle from './StyleSet/CodeBlockCopyButton'; import createConnectivityNotification from './StyleSet/ConnectivityNotification'; import createDictationInterimsStyle from './StyleSet/DictationInterims'; import createErrorBoxStyle from './StyleSet/ErrorBox'; @@ -47,7 +45,6 @@ import createTypingIndicatorStyle from './StyleSet/TypingIndicator'; import createUploadButtonStyle from './StyleSet/UploadButton'; import createVideoAttachmentStyle from './StyleSet/VideoAttachment'; import createVideoContentStyle from './StyleSet/VideoContent'; -import createViewCodeDialogStyle from './StyleSet/ViewCodeDialog'; import createVimeoContentStyle from './StyleSet/VimeoContent'; import createWarningNotificationStyle from './StyleSet/WarningNotification'; import createYouTubeContentStyle from './StyleSet/YouTubeContent'; @@ -72,8 +69,6 @@ export default function createStyleSet(styleOptions: StyleOptions) { carouselFilmStrip: createCarouselFilmStrip(strictStyleOptions), carouselFilmStripAttachment: createCarouselFilmStripAttachment(strictStyleOptions), carouselFlipper: createCarouselFlipper(strictStyleOptions), - codeBlock: createCodeBlockStyle(), - codeBlockCopyButton: createCodeBlockCopyButtonStyle(), connectivityNotification: createConnectivityNotification(strictStyleOptions), dictationInterims: createDictationInterimsStyle(strictStyleOptions), errorBox: createErrorBoxStyle(strictStyleOptions), @@ -113,7 +108,6 @@ export default function createStyleSet(styleOptions: StyleOptions) { sendStatus: createSendStatusStyle(), textContent: createTextContentStyle(), thumbButton: createThumbButtonStyle(), - tooltip: createTooltipStyle(), - viewCodeDialog: createViewCodeDialogStyle() + tooltip: createTooltipStyle() } as const); } diff --git a/packages/component/src/Utils/tabbableElements.ts b/packages/component/src/Utils/tabbableElements.ts index 859fa48103..2091a9c60e 100644 --- a/packages/component/src/Utils/tabbableElements.ts +++ b/packages/component/src/Utils/tabbableElements.ts @@ -7,9 +7,15 @@ export default function tabbableElements(element?: HTMLElement): HTMLElement[] { ) || []; return ([] as HTMLElement[]).filter.call(candidates, (element: HTMLElement) => { + const style = window.getComputedStyle(element); + + if (style.visibility === 'hidden' || style.display === 'none' || style.display === 'contents') { + return false; + } + const tabIndexAttribute = element.attributes.getNamedItem('tabindex'); - if (tabIndexAttribute && tabIndexAttribute.specified) { + if (tabIndexAttribute) { const value = parseInt(tabIndexAttribute.value, 10); return value >= 0 || (isNaN(value) && element.nodeName.toLowerCase() === 'input'); diff --git a/packages/component/src/providers/CustomElements/customElements/CodeBlock.module.css b/packages/component/src/providers/CustomElements/customElements/CodeBlock.module.css new file mode 100644 index 0000000000..6540360048 --- /dev/null +++ b/packages/component/src/providers/CustomElements/customElements/CodeBlock.module.css @@ -0,0 +1,35 @@ +:global(.webchat) .code-block { + background: var(--webchat__background--code-block, inherit); + border: 1px solid #d1d1d1; + border-radius: 4px; + color: var(--webchat__color--code-block, currentColor); + display: block; + margin: 16px 0; + overflow: hidden; + padding: 4px 4px 4px 8px; + line-height: normal; +} + +:global(.webchat) .code-block:has(.code-block__theme--github-dark-default) { + background: var(--webchat__background--code-block, #0d1117); + color: var(--webchat__color--code-block, #e6edf3); +} + +:global(.webchat) .code-block:has(.code-block__theme--github-light-default) { + background: var(--webchat__background--code-block, #ffffff); + color: var(--webchat__color--code-block, #1f2328); +} + +:global(.webchat) .code-block:has(.code-block__body:focus-visible):focus-within { + outline: 2px solid #000; + outline-offset: -2px; +} + +:global(.webchat) .code-block__body { + margin: 0; + outline: none; + white-space: pre-wrap; + /* TODO: align with the rest of code blocks */ + line-height: unset; + display: inline; +} diff --git a/packages/component/src/providers/CustomElements/customElements/CodeBlock.ts b/packages/component/src/providers/CustomElements/customElements/CodeBlock.ts index 17283563f1..b91c9d4fb1 100644 --- a/packages/component/src/providers/CustomElements/customElements/CodeBlock.ts +++ b/packages/component/src/providers/CustomElements/customElements/CodeBlock.ts @@ -1,11 +1,13 @@ /* eslint-disable class-methods-use-this */ import { hooks } from 'botframework-webchat-api'; +import { useStyles } from '@msinternal/botframework-webchat-styles/react'; import { ReactNode, useMemo, useRef } from 'react'; -import { useStyleSet } from '../../../hooks'; import { defaultHighlightCode, HighlightCodeFn } from '../../../hooks/internal/codeHighlighter/index'; import { parseDocumentFragmentFromString, useCodeHighlighter } from '../../../internal'; +import styles from './CodeBlock.module.css'; + const { useStyleOptions, useLocalizer } = hooks; class CodeBlock extends HTMLElement { @@ -82,11 +84,6 @@ class CodeBlock extends HTMLElement { const highlightedCodeFragment = result instanceof DocumentFragment ? result : parseDocumentFragmentFromString(result); - const body = highlightedCodeFragment.querySelector('pre'); - - body?.classList.add('webchat__code-block__body'); - options?.theme && body?.classList.add(`webchat__code-block__theme--${options.theme}`); - highlightedCodeFragment.insertBefore(this.copyButtonElement, highlightedCodeFragment.firstChild); this.replaceChildren(highlightedCodeFragment); @@ -109,11 +106,9 @@ export type CodeBlockProps = Readonly<{ }>; type CodeBlockReactProps = Readonly<{ - codeBlockClass: string | undefined; codeBlockTheme: 'github-light-default' | 'github-dark-default'; copyButtonAltCopied: string; copyButtonAltCopy: string; - copyButtonClassName: string; copyButtonTagName: string; highlightCode: HighlightCodeFn; }>; @@ -121,7 +116,6 @@ type CodeBlockReactProps = Readonly<{ const useCodeBlockProps = (copyButtonTagName: string) => { const highlightCode = useCodeHighlighter(); const localize = useLocalizer(); - const [{ codeBlock: codeBlockClass, codeBlockCopyButton: copyButtonClassName }] = useStyleSet(); const [{ codeBlockTheme }] = useStyleOptions(); const copyButtonAltCopied = localize('COPY_BUTTON_COPIED_TEXT'); const copyButtonAltCopy = localize('COPY_BUTTON_TEXT'); @@ -130,22 +124,18 @@ const useCodeBlockProps = (copyButtonTagName: string) => { useMemo(() => { propsRef.current = Object.freeze({ - codeBlockClass, codeBlockTheme, copyButtonAltCopied, copyButtonAltCopy, - copyButtonClassName, copyButtonTagName, highlightCode }); propsChangedEventTarget.dispatchEvent(new CustomEvent('change')); }, [ - codeBlockClass, codeBlockTheme, copyButtonAltCopied, copyButtonAltCopy, - copyButtonClassName, copyButtonTagName, highlightCode, propsChangedEventTarget @@ -160,6 +150,8 @@ const useCodeBlockProps = (copyButtonTagName: string) => { export default function useReactCodeBlockClass(copyButtonTagName: string) { const [codeBlockTarget, codeBlockPropsRef] = useCodeBlockProps(copyButtonTagName); + const classNames = useStyles(styles); + return useMemo( () => class ReactCodeBlock extends CodeBlock { @@ -177,11 +169,6 @@ export default function useReactCodeBlockClass(copyButtonTagName: string) { this.#prevProps = props; - if (prevProps?.codeBlockClass !== props?.codeBlockClass) { - prevProps?.codeBlockClass && this.classList.remove(prevProps.codeBlockClass); - props?.codeBlockClass && this.classList.add(props.codeBlockClass); - } - this.setAttribute('theme', props.codeBlockTheme); if (prevProps?.highlightCode !== props.highlightCode) { @@ -195,20 +182,31 @@ export default function useReactCodeBlockClass(copyButtonTagName: string) { this.scheduleUpdate(); } - this.copyButtonElement.className = props.copyButtonClassName; this.copyButtonElement.dataset.altCopy = props.copyButtonAltCopy; this.copyButtonElement.dataset.altCopied = props.copyButtonAltCopied; }; connectedCallback(): void { - this.classList.add('webchat__code-block'); - + this.classList.add(...classNames['code-block'].split(/\s+/gu)); codeBlockTarget.addEventListener('change', this.#handlePropsChange); this.#handlePropsChange(); super.connectedCallback(); } + update(): void { + super.update(); + + const body = this.querySelector('pre'); + body?.classList.add(...classNames['code-block__body'].split(/\s+/gu)); + + const theme = this.options?.theme; + theme && + body?.classList.add( + ...(classNames[`code-block__theme--${theme}`] ?? `code-block__theme--${theme}`).split(/\s+/gu) + ); + } + disconnectedCallback(): void { codeBlockTarget.removeEventListener('change', this.#handlePropsChange); @@ -225,6 +223,6 @@ export default function useReactCodeBlockClass(copyButtonTagName: string) { return this.#props.highlightCode(...args); } }, - [codeBlockPropsRef, codeBlockTarget] + [classNames, codeBlockPropsRef, codeBlockTarget] ); } diff --git a/packages/component/src/providers/CustomElements/customElements/CodeBlockCopyButton.module.css b/packages/component/src/providers/CustomElements/customElements/CodeBlockCopyButton.module.css new file mode 100644 index 0000000000..80a201a57c --- /dev/null +++ b/packages/component/src/providers/CustomElements/customElements/CodeBlockCopyButton.module.css @@ -0,0 +1,63 @@ +:global(.webchat) .code-block-copy-button { + align-items: center; + appearance: none; + background: #fff; + border-radius: 4px; + border: 1px solid #d1d1d1; + color: #242424; + display: grid; + float: right; /* Use "inline-end" after we update Chromium in Docker */ + gap: 4px; + justify-content: center; + margin-inline-start: var(--webchat__padding--regular); + padding: 3px; +} + +:global(.webchat) .code-block-copy-button:hover { + background: #f5f5f5; + border: 1px solid #c7c7c7; + color: #242424; +} + +:global(.webchat) .code-block-copy-button:active { + background: #e0e0e0; + border: 1px solid #b3b3b3; + color: #242424; +} + +:global(.webchat) .code-block-copy-button:focus-visible { + background: #fff; + outline: 2px solid #000; + outline-offset: -2px; +} + +:global(.webchat) .code-block-copy-button[aria-disabled="true"] { + background: #f0f0f0; + border: 1px solid #e0e0e0; + color: #bdbdbd; + cursor: not-allowed; +} + +:global(.webchat) .code-block-copy-button__icon { + grid-column: 1; + grid-row: 1; + --webchat__component-icon--color: #707070; + --webchat__component-icon--size: 16px; +} + +:global(.webchat) .code-block-copy-button__icon--copied { + opacity: 0; +} + +:global(.webchat) .code-block-copy-button--copied .code-block-copy-button__icon { + animation: code-block-copy-button__copied-animation 0.5s linear; +} + +@keyframes code-block-copy-button__copied-animation { + 0% { + opacity: 100%; + } + 100% { + opacity: unset; + } +} diff --git a/packages/component/src/providers/CustomElements/customElements/CodeBlockCopyButton.tsx b/packages/component/src/providers/CustomElements/customElements/CodeBlockCopyButton.tsx index 87fca2be94..ae69548471 100644 --- a/packages/component/src/providers/CustomElements/customElements/CodeBlockCopyButton.tsx +++ b/packages/component/src/providers/CustomElements/customElements/CodeBlockCopyButton.tsx @@ -1,4 +1,5 @@ -import classNames from 'classnames'; +import cx from 'classnames'; +import { useStyles } from '@msinternal/botframework-webchat-styles/react'; import React, { memo, useCallback, useState } from 'react'; import { useRefFrom } from 'use-ref-from'; import { useStateWithRef } from 'use-state-with-ref'; @@ -7,6 +8,8 @@ import { ComponentIcon } from '../../../Icon'; import testIds from '../../../testIds'; import wrapAsCustomElement from './wrapAsCustomElement'; +import styles from './CodeBlockCopyButton.module.css'; + export type CodeBlockCopyButtonProps = Readonly<{ className: string; 'data-alt-copied': string; @@ -21,6 +24,7 @@ const CodeBlockCopyButton = memo( 'data-alt-copy': copyAlt, 'data-value': value }: CodeBlockCopyButtonProps) => { + const classNames = useStyles(styles); const [disabled, setDisabled, disabledRef] = useStateWithRef(false); const [pressed, setPressed] = useState(false); const valueRef = useRefFrom(value); @@ -64,8 +68,8 @@ const CodeBlockCopyButton = memo( aria-disabled={disabled} aria-live="assertive" aria-pressed={pressed ? 'true' : undefined} - className={classNames(className, 'webchat__code-block-copy-button', { - 'webchat__code-block-copy-button--copied': pressed + className={cx(className, classNames['code-block-copy-button'], { + [classNames['code-block-copy-button--copied']]: pressed })} data-testid={testIds.codeBlockCopyButton} onAnimationEnd={handleAnimationEnd} @@ -75,14 +79,14 @@ const CodeBlockCopyButton = memo(
diff --git a/packages/fluent-theme/src/components/activity/PartGroupingDecorator.module.css b/packages/fluent-theme/src/components/activity/PartGroupingDecorator.module.css index dac5f616d7..c5fb8790d6 100644 --- a/packages/fluent-theme/src/components/activity/PartGroupingDecorator.module.css +++ b/packages/fluent-theme/src/components/activity/PartGroupingDecorator.module.css @@ -268,7 +268,7 @@ padding-inline: var(--webchat-spacingHorizontalL) var(--webchat-spacingHorizontalS); } - :global(.webchat__code-block-copy-button) { + :global(.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 f0b6d5f002..fea5d5f2be 100644 --- a/packages/fluent-theme/src/components/theme/Theme.module.css +++ b/packages/fluent-theme/src/components/theme/Theme.module.css @@ -231,8 +231,8 @@ :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(.webchat__render-markdown [data-math-type='block']), -:global(.webchat-fluent).theme :global(.webchat__view-code-dialog__body), -:global(.webchat-fluent).theme :global(.webchat__view-code-dialog__code-body) { +:global(.webchat-fluent).theme :global(.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; @@ -444,7 +444,7 @@ } /* Code block */ -:global(.webchat-fluent).theme :global(.webchat__code-block) { +:global(.webchat-fluent).theme :global(.code-block) { border: none; font-family: var(--webchat-fontFamilyMonospace); font-size: var(--webchat-fontSizeBase300); @@ -452,7 +452,7 @@ --webchat__code-block__copy-button--color: var(--webchat-colorNeutralForeground1); --webchat__code-block__copy-button--background: var(--webchat-colorNeutralBackground3); - :global(.webchat__code-block-copy-button) { + :global(.code-block-copy-button) { background: transparent; border: none; color: var(--webchat__code-block__copy-button--color); @@ -462,7 +462,7 @@ width: 20px; transition: background-color var(--webchat-durationNormal) var(--webchat-curveDecelerateMid); - :global(.webchat__code-block-copy-button__icon) { + :global(.code-block-copy-button__icon) { --webchat__component-icon--color: currentColor; } @@ -482,16 +482,21 @@ } } - &:global(:has(.webchat__code-block__body:focus-visible)):focus-within { + &:global(:has(.code-block__body:focus-visible)):focus-within { outline: var(--webchat-strokeWidthThick) solid var(--webchat-colorStrokeFocus2); outline-offset: calc(var(--webchat-strokeWidthThick) * -1); - :global(.webchat__code-block__body) { + :global(.code-block__body) { outline: none; } } - &:global(:has(> .webchat__code-block__theme--github-dark-default)) { + :global(.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)) { --webchat__background--code-block: var(--codeBlockBackground, var(--webchat-colorGrey8)); --webchat__code-block__copy-button--background: var( --codeBlockCopyButtonBackgroundPressed, @@ -501,7 +506,7 @@ --webchat__color--code-block: var(--codeBlockForeground, var(--webchat-colorGrey98)); } - &:global(:has(> .webchat__code-block__theme--github-light-default)) { + &:global(: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)); @@ -526,7 +531,7 @@ width: unset; } - :global(.webchat__code-block-copy-button) { + :global(.code-block-copy-button) { --webchat__code-block__copy-button--color: var(--webchat-colorNeutralForeground1); --webchat__code-block__copy-button--background: var(--webchat-colorNeutralBackground3); @@ -545,20 +550,25 @@ padding: var(--webchat-spacingVerticalS) var(--webchat-spacingHorizontalM); } - :global(.webchat__view-code-dialog__title) { + :global(.view-code-dialog__title) { font-size: var(--webchat-fontSizeBase300); font-weight: var(--webchat-fontWeightSemibold); line-height: var(--webchat-lineHeightBase300); } - :global(.webchat__view-code-dialog__body) { + :global(.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(.webchat__view-code-dialog__footer) { + :global(.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) { color: var(--webchat-colorNeutralForeground4); font-size: var(--webchat-fontSizeBase100); line-height: var(--webchat-lineHeightBase100);