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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified __tests__/html2/activity/collapsible.copilot.dark.html.snap-4.png
Comment thread
compulim marked this conversation as resolved.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified __tests__/html2/activity/collapsible.copilot.dark.html.snap-5.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified __tests__/html2/activity/collapsible.copilot.dark.html.snap-6.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified __tests__/html2/activity/collapsible.copilot.dark.html.snap-7.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified __tests__/html2/activity/collapsible.copilot.dark.html.snap-8.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified __tests__/html2/activity/collapsible.copilot.html.snap-4.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified __tests__/html2/activity/collapsible.copilot.html.snap-5.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified __tests__/html2/activity/collapsible.copilot.html.snap-6.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified __tests__/html2/activity/collapsible.copilot.html.snap-7.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified __tests__/html2/activity/collapsible.copilot.html.snap-8.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified __tests__/html2/activity/collapsible.fluent.dark.html.snap-4.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified __tests__/html2/activity/collapsible.fluent.dark.html.snap-5.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified __tests__/html2/activity/collapsible.fluent.dark.html.snap-6.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified __tests__/html2/activity/collapsible.fluent.dark.html.snap-7.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified __tests__/html2/activity/collapsible.fluent.dark.html.snap-8.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified __tests__/html2/activity/collapsible.fluent.html.snap-4.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified __tests__/html2/activity/collapsible.fluent.html.snap-5.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified __tests__/html2/activity/collapsible.fluent.html.snap-6.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified __tests__/html2/activity/collapsible.fluent.html.snap-7.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified __tests__/html2/activity/collapsible.fluent.html.snap-8.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
148 changes: 148 additions & 0 deletions __tests__/html2/activity/viewCodeButton.scroll.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
<!doctype html>
<html lang="en-US">
<head>
<link href="/assets/index.css" rel="stylesheet" type="text/css" />
<script crossorigin="anonymous" src="https://unpkg.com/@babel/standalone@7.8.7/babel.min.js"></script>
<script crossorigin="anonymous" src="https://unpkg.com/react@16.8.6/umd/react.production.min.js"></script>
<script crossorigin="anonymous" src="https://unpkg.com/react-dom@16.8.6/umd/react-dom.production.min.js"></script>
<script crossorigin="anonymous" src="/test-harness.js"></script>
<script crossorigin="anonymous" src="/test-page-object.js"></script>
<script crossorigin="anonymous" src="/__dist__/webchat-es5.js"></script>
</head>
<body>
<main id="webchat"></main>
<script type="text/babel">
run(async function () {
const {
React,
ReactDOM: { render },
WebChat: { ReactWebChat }
} = window; // Imports in UMD fashion.

const aiMessageEntity = {
'@context': 'https://schema.org',
'@id': '',
'@type': 'Message',
keywords: ['AIGeneratedContent', 'AllowCopy'],
type: 'https://schema.org/Message'
};

const waveSvg = `data:image/svg+xml;utf8,${encodeURIComponent(`
<svg width="400" height="200" viewBox="0 0 400 200" xmlns="http://www.w3.org/2000/svg">
<!-- Primary Wave -->
<path d="M0,100 C50,50 100,150 150,100 C200,50 250,150 300,100 C350,50 400,150 400,100"
stroke="#3B82F6" fill="none" stroke-width="2" opacity="0.5"/>

<!-- Second Harmonic -->
<path d="M0,100 C25,75 50,125 75,100 C100,75 125,125 150,100 C175,75 200,125 225,100 C250,75 275,125 300,100 C325,75 350,125 375,100 C400,75 400,125 400,100"
stroke="#10B981" fill="none" stroke-width="2" opacity="0.5"/>

<!-- Combined Wave -->
<path d="M0,100 C40,30 80,170 120,100 C160,30 200,170 240,100 C280,30 320,170 360,100 C380,65 400,135 400,100"
stroke="#EF4444" fill="none" stroke-width="3"/>

<!-- Grid Lines -->
<line x1="0" y1="100" x2="400" y2="100" stroke="#CBD5E1" stroke-width="0.5" stroke-dasharray="4"/>
<line x1="100" y1="0" x2="100" y2="200" stroke="#CBD5E1" stroke-width="0.5" stroke-dasharray="4"/>
<line x1="200" y1="0" x2="200" y2="200" stroke="#CBD5E1" stroke-width="0.5" stroke-dasharray="4"/>
<line x1="300" y1="0" x2="300" y2="200" stroke="#CBD5E1" stroke-width="0.5" stroke-dasharray="4"/>
</svg>`)}`;

const { directLine, store } = testHelpers.createDirectLineEmulator();

render(
<ReactWebChat directLine={directLine} store={store} />,
document.getElementById('webchat')
);

await pageConditions.uiConnected();

directLine.emulateIncomingActivity({
from: { role: 'bot' },
entities: [{
...aiMessageEntity,
isBasedOn: {
'@type': 'SoftwareSourceCode',
'programmingLanguage': 'python',
"text": `import numpy as np
import matplotlib.pyplot as plt

def plot_sine_waves():
"""Create a beautiful visualization of sine waves with different frequencies."""
# Generate time points
t = np.linspace(0, 10, 1000)

# Create waves with different frequencies and phases
wave1 = np.sin(t)
wave2 = 0.5 * np.sin(2 * t + np.pi/4)
wave3 = 0.3 * np.sin(3 * t + np.pi/3)

# Combine waves
combined = wave1 + wave2 + wave3

# Create a stylish plot
plt.style.use('seaborn-darkgrid')
plt.figure(figsize=(12, 8))

# Plot individual waves
plt.plot(t, wave1, label='Primary Wave', alpha=0.5)
plt.plot(t, wave2, label='Second Harmonic', alpha=0.5)
plt.plot(t, wave3, label='Third Harmonic', alpha=0.5)

# Plot combined wave with a thicker line
plt.plot(t, combined, 'r-',
label='Combined Wave',
linewidth=2)

plt.title('Harmonic Wave Composition', fontsize=14)
plt.xlabel('Time', fontsize=12)
plt.ylabel('Amplitude', fontsize=12)
plt.legend()
plt.grid(True, alpha=0.3)

# Show the plot
plt.tight_layout()
plt.show()

# Generate the visualization
plot_sine_waves()`
}
}],
type: "message",
text: `This example demonstrates creating a beautiful visualization of harmonic waves using Python's Matplotlib library. The code generates three sine waves with different frequencies and phases, then combines them to show wave interference patterns.\n<img alt="wave plot" src="${waveSvg}">`,
});

await pageConditions.numActivitiesShown(1);

// When: Focus the "Code" button
const codeButton = document.querySelector(`[data-testid="${WebChat.testIds.viewCodeButton}"]`);
codeButton.focus();

// Then: Shows a focus ring around the "Code" button
expect(document.activeElement).toBe(codeButton);

// WHEN: Press ENTER on the "Code" button.
await host.sendKeys('ENTER');

// WHEN: Press Tab to focus copy button.
await host.sendTab();

// THEN: Shows a focus ring around the "Copy" button.
await host.snapshot('local');

// WHEN: Press Tab to focus code block.
await host.sendTab();

// THEN: Shows a focus ring around the code block.
await host.snapshot('local');

// WHEN: Press ARROW_DOWN to scroll down the code block.
await host.sendKeys('ARROW_DOWN');
await host.sendKeys('ARROW_DOWN');

// // THEN: The code block should scroll down.
await host.snapshot('local');
});
</script>
</body>
</html>
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Comment thread
compulim marked this conversation as resolved.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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';

Expand All @@ -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(
Expand All @@ -27,8 +30,9 @@ type ActivityViewCodeButtonProps = InferInput<typeof activityViewCodeButtonProps

const ActivityViewCodeButton = (props: ActivityViewCodeButtonProps) => {
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();

Expand All @@ -37,22 +41,22 @@ const ActivityViewCodeButton = (props: ActivityViewCodeButtonProps) => {
() => (
<CodeContent code={code} language={language} title={title}>
{isAIGenerated && (
<div className={'webchat__view-code-dialog__footer'}>
<div className={classNames['view-code-dialog__footer']}>
<LocalizedString linkClassName={'webchat__view-code-dialog__link'} stringIds="ACTIVITY_CODE_CAUTION" />
</div>
)}
</CodeContent>
),
{
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 (
<ActivityButton
className={classNames(activityButton, 'webchat__activity-button', className)}
className={cx(activityButton, 'webchat__activity-button', className)}
data-testid="view code button"
icon="view-code"
onClick={showCodeModal}
Expand Down
12 changes: 8 additions & 4 deletions packages/component/src/Attachment/Text/private/CodeContent.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { reactNode, validateProps } from '@msinternal/botframework-webchat-react-valibot';
import classNames from 'classnames';
import { useStyles } from '@msinternal/botframework-webchat-styles/react';
import cx from 'classnames';
import React, { Fragment, memo } from 'react';
import { object, optional, pipe, readonly, string, type InferInput } from 'valibot';

import useCodeBlockTag from '../../../providers/CustomElements/useCodeBlockTagName';

import styles from './ViewCodeDialog.module.css';

const codeContentPropsSchema = pipe(
object({
children: optional(reactNode()),
Expand All @@ -22,13 +25,14 @@ function CodeContent(props: CodeContentProps) {
const { children, className, code, language, title } = validateProps(codeContentPropsSchema, props);

const [, CodeBlock] = useCodeBlockTag();
const classNames = useStyles(styles);

return (
<Fragment>
<div className={'webchat__view-code-dialog__header'}>
<h2 className={'webchat__view-code-dialog__title'}>{title}</h2>
<div className={classNames['view-code-dialog__header']}>
<h2 className={classNames['view-code-dialog__title']}>{title}</h2>
</div>
<CodeBlock className={classNames('webchat__view-code-dialog__body', className)} language={language}>
<CodeBlock className={cx(classNames['view-code-dialog__body'], className)} language={language}>
<code>{code}</code>
</CodeBlock>
{children}
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}

8 changes: 1 addition & 7 deletions packages/component/src/Styles/createStyleSet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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';
Expand All @@ -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),
Expand Down Expand Up @@ -113,7 +108,6 @@ export default function createStyleSet(styleOptions: StyleOptions) {
sendStatus: createSendStatusStyle(),
textContent: createTextContentStyle(),
thumbButton: createThumbButtonStyle(),
tooltip: createTooltipStyle(),
viewCodeDialog: createViewCodeDialogStyle()
tooltip: createTooltipStyle()
} as const);
}
8 changes: 7 additions & 1 deletion packages/component/src/Utils/tabbableElements.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
Loading
Loading