-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Add copy button * Add copy button tests * Put copy button inside bubble * Fix screenshot * Fix build pipeline * Localize copy text * Hide toolbox when not needed * Loosen valibot parse * Fix copy button text * Use CSS animation to flip copied text * Add clipboard permissions * Add Copy button entry * Fallback properly * Use keyboard to tap the Copy button * Put "Copy" button under main text only * Clean up * Remove unused var * Incorporate PR feedback * Incorporate PR feedbacks * Incorporate PR feedbacks * Incorporate PR feedbacks * Add tests for dark vs. light and Fluent vs. Copilot * Sort/dedupe CSS vars * Prefer backgroundDisabled than background1Disabled * Fixing padding-block
- Loading branch information
Showing
39 changed files
with
2,903 additions
and
51 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Binary file added
BIN
+17.8 KB
...s__/html/copy-button-js-fluent-theme-applied-copy-button-should-work-1-snap.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 added
BIN
+17.8 KB
...s__/html/copy-button-js-fluent-theme-applied-copy-button-should-work-2-snap.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 added
BIN
+26.9 KB
...s__/html/copy-button-js-fluent-theme-applied-copy-button-should-work-3-snap.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 added
BIN
+19.8 KB
...th-dark-theme-and-copilot-variant-copy-button-should-layout-properly-1-snap.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 added
BIN
+20.7 KB
...ith-dark-theme-and-fluent-variant-copy-button-should-layout-properly-1-snap.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 added
BIN
+19.8 KB
...h-light-theme-and-copilot-variant-copy-button-should-layout-properly-1-snap.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 added
BIN
+21 KB
...th-light-theme-and-fluent-variant-copy-button-should-layout-properly-1-snap.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
<!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/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> | ||
<script crossorigin="anonymous" src="/__dist__/botframework-webchat-fluent-theme.production.min.js"></script> | ||
</head> | ||
<body> | ||
<main id="webchat" style="position: relative"></main> | ||
<script type="text/babel"> | ||
run(async function () { | ||
const { | ||
React, | ||
ReactDOM: { render }, | ||
WebChat: { FluentThemeProvider, ReactWebChat } | ||
} = window; // Imports in UMD fashion. | ||
|
||
const { directLine, store } = testHelpers.createDirectLineEmulator(); | ||
|
||
const App = () => ( | ||
<React.Fragment> | ||
<ReactWebChat directLine={directLine} store={store} /> | ||
<div style={{ gap: 8, position: 'absolute', top: 0, width: '100%' }}> | ||
<label> | ||
<div>Plain text box</div> | ||
<input | ||
data-testid="plain-text-box" | ||
spellCheck={false} | ||
style={{ background: '#f0f0f0', border: 0, height: 50, padding: 0, width: '100%' }} | ||
type="textbox" | ||
/> | ||
</label> | ||
<label> | ||
<div>Rich text box</div> | ||
<div | ||
contentEditable={true} | ||
data-testid="rich-text-box" | ||
spellCheck={false} | ||
style={{ background: '#f0f0f0', border: 0, height: 50, width: '100%' }} | ||
/> | ||
</label> | ||
</div> | ||
</React.Fragment> | ||
); | ||
|
||
render( | ||
<FluentThemeProvider> | ||
<App /> | ||
</FluentThemeProvider>, | ||
document.getElementById('webchat') | ||
); | ||
|
||
await pageConditions.uiConnected(); | ||
|
||
expect(window.isSecureContext).toBe(true); | ||
|
||
await host.sendDevToolsCommand('Browser.setPermission', { | ||
permission: { name: 'clipboard-write' }, | ||
setting: 'granted' | ||
}); | ||
|
||
await expect(navigator.permissions.query({ name: 'clipboard-write' })).resolves.toHaveProperty( | ||
'state', | ||
'granted' | ||
); | ||
|
||
await directLine.emulateIncomingActivity({ | ||
entities: [ | ||
{ | ||
'@context': 'https://schema.org', | ||
'@id': '', | ||
'@type': 'Message', | ||
keywords: ['AllowCopy'], | ||
type: 'https://schema.org/Message' | ||
} | ||
], | ||
text: 'Mollit *aute* **aute** dolor ea ex magna incididunt nostrud sit nisi.', | ||
type: 'message' | ||
}); | ||
|
||
await pageConditions.numActivitiesShown(1); | ||
|
||
// WHEN: Focus on the "Copy" button via keyboard. | ||
await host.click(document.querySelector(`[data-testid="${WebChat.testIds.sendBoxTextBox}"]`)); | ||
await host.sendShiftTab(2); | ||
await host.sendKeys('ENTER'); | ||
|
||
// THEN: Should focus on the "Copy" button | ||
const copyButton = document.querySelector(`[data-testid="${WebChat.testIds.copyButton}"]`); | ||
|
||
expect(document.activeElement).toBe(copyButton); | ||
await host.snapshot(); | ||
|
||
// WHEN: Press ENTER on the "Copy" button. | ||
await host.sendKeys('ENTER'); | ||
|
||
// THEN: The copy button should change to "Copied". | ||
await host.snapshot(); | ||
|
||
// WHEN: Paste into plain text and rich text text box. | ||
await host.click(document.querySelector('[data-testid="plain-text-box"]')); | ||
await host.sendKeys('+CONTROL', 'v', '-CONTROL'); | ||
|
||
await host.click(document.querySelector('[data-testid="rich-text-box"]')); | ||
await host.sendKeys('+CONTROL', 'v', '-CONTROL'); | ||
|
||
await host.click(document.querySelector(`[data-testid="${WebChat.testIds.sendBoxTextBox}"]`)); | ||
|
||
// Sleep for 1 second for the "Copied" text to go away. | ||
await testHelpers.sleep(500); | ||
|
||
// THEN: Plain text box should contains plain text, while rich text box should contains rich text. | ||
await host.snapshot(); | ||
}); | ||
</script> | ||
</body> | ||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
/** @jest-environment ./packages/test/harness/src/host/jest/WebDriverEnvironment.js */ | ||
|
||
describe('Fluent theme applied', () => { | ||
test('copy button should work', () => runHTML('fluentTheme/copyButton')); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
<!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/babel.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__/fluent-bundle.production.min.js"></script> | ||
<script crossorigin="anonymous" src="/__dist__/webchat-es5.js"></script> | ||
<script crossorigin="anonymous" src="/__dist__/botframework-webchat-fluent-theme.production.min.js"></script> | ||
<style> | ||
.fui-FluentProvider { | ||
display: contents; | ||
|
||
--webchat__color--surface: var(--colorNeutralBackground3); | ||
} | ||
</style> | ||
</head> | ||
<body> | ||
<main id="webchat" style="position: relative"></main> | ||
<script type="text/babel"> | ||
run(async function () { | ||
const { | ||
Fluent: { createDarkTheme, createLightTheme, FluentProvider }, | ||
React, | ||
ReactDOMClient: { createRoot }, | ||
WebChat: { FluentThemeProvider, ReactWebChat } | ||
} = window; // Imports in UMD fashion. | ||
|
||
const { directLine, store } = testHelpers.createDirectLineEmulator(); | ||
const searchParams = new URLSearchParams(location.search); | ||
const styleOptions = { | ||
botAvatarBackgroundColor: '#304E7A', | ||
botAvatarImage: | ||
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAAK0lEQVQ4T2P8z8Dwn4GKgHHUQIpDczQMKQ5ChtEwHA1DMkJgNNmQEWhoWgBMAiftPRtHngAAAABJRU5ErkJggg==' | ||
}; | ||
|
||
const App = () => <ReactWebChat directLine={directLine} store={store} styleOptions={styleOptions} />; | ||
|
||
const customBrandRamp = { | ||
10: '#124C32', | ||
20: '#1A5B3E', | ||
30: '#216A4A', | ||
40: '#297956', | ||
50: '#308861', | ||
60: '#38976D', | ||
70: '#40A779', | ||
80: '#158051', | ||
90: '#4FC590', | ||
100: '#56D49C', | ||
110: '#5EE3A8', | ||
120: '#79E8B7', | ||
130: '#94ECC5', | ||
140: '#AFF1D3', | ||
150: '#C9F6E2', | ||
160: '#E4FAF1' | ||
}; | ||
|
||
const root = createRoot(document.getElementById('webchat')); | ||
|
||
root.render( | ||
<FluentProvider | ||
theme={ | ||
searchParams.get('theme') === 'dark' | ||
? createDarkTheme(customBrandRamp) | ||
: createLightTheme(customBrandRamp) | ||
} | ||
> | ||
<FluentThemeProvider variant={searchParams.get('variant') || ''}> | ||
<App /> | ||
</FluentThemeProvider> | ||
</FluentProvider> | ||
); | ||
|
||
await pageConditions.uiConnected(); | ||
|
||
await directLine.emulateIncomingActivity({ | ||
// TODO: Attachment is buggy now: clipped into the text content now and not aligned horizontally. | ||
// attachments: [ | ||
// { | ||
// contentType: 'image/jpeg', | ||
// contentUrl: | ||
// 'https://raw.githubusercontent.com/compulim/BotFramework-MockBot/master/public/assets/surface1.jpg' | ||
// } | ||
// ], | ||
entities: [ | ||
{ | ||
'@context': 'https://schema.org', | ||
'@id': '', | ||
'@type': 'Message', | ||
keywords: ['AllowCopy'], | ||
type: 'https://schema.org/Message', | ||
citation: [ | ||
{ | ||
'@id': | ||
'https://bing.com/', | ||
'@type': 'Claim', | ||
claimInterpreter: { | ||
'@type': 'Project', | ||
slogan: 'Surfaced with Azure OpenAI', | ||
url: 'https://www.microsoft.com/en-us/ai/responsible-ai' | ||
}, | ||
position: '1' | ||
} | ||
] | ||
} | ||
], | ||
from: { | ||
name: 'Copilot', | ||
role: 'bot' | ||
}, | ||
text: 'Mollit *aute* **aute** dolor[1] ea ex magna incididunt nostrud sit nisi.\n\n[1]: https://bing.com/ "Ex voluptate est dolore"', | ||
type: 'message' | ||
}); | ||
|
||
await pageConditions.numActivitiesShown(1); | ||
|
||
// THEN: "Copy" button should appear after the message. | ||
await host.snapshot(); | ||
}); | ||
</script> | ||
</body> | ||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
/** @jest-environment ./packages/test/harness/src/host/jest/WebDriverEnvironment.js */ | ||
|
||
describe('Fluent theme applied', () => { | ||
describe.each([ | ||
['dark', 'fluent'], | ||
['dark', 'copilot'], | ||
['light', 'fluent'], | ||
['light', 'copilot'] | ||
])('with %s theme and %s variant', (theme, variant) => | ||
test('copy button should layout properly', () => | ||
runHTML(`fluentTheme/copyButton.layout?${new URLSearchParams({ theme, variant }).toString()}`)) | ||
); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
6 changes: 6 additions & 0 deletions
6
packages/fluent-theme/src/components/activityToolbox/ActivityToolbox.module.css
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
:global(.webchat-fluent) .activity-toolbox { | ||
display: flex; | ||
padding-block: var(--webchat__bubble--block-padding); | ||
padding-block-start: 0; /* Specifically set block-start to 0. */ | ||
padding-inline: var(--webchat__bubble--inline-padding); | ||
} |
31 changes: 31 additions & 0 deletions
31
packages/fluent-theme/src/components/activityToolbox/ActivityToolbox.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import type { WebChatActivity } from 'botframework-webchat-core'; | ||
import React, { memo, useMemo } from 'react'; | ||
import { array, looseObject, optional, parse, string } from 'valibot'; | ||
import { useStyles } from '../../styles'; | ||
import getMessageEntity from '../../utils/getMessageEntity'; | ||
import styles from './ActivityToolbox.module.css'; | ||
import CopyButton from './CopyButton'; | ||
|
||
type Props = Readonly<{ activity: WebChatActivity }>; | ||
|
||
const activitySchema = looseObject({ | ||
entities: optional(array(looseObject({ type: string() }))), | ||
type: string() | ||
}); | ||
|
||
const ActivityToolbox = (props: Props) => { | ||
const activity = useMemo(() => parse(activitySchema, props.activity), [props.activity]); | ||
const classNames = useStyles(styles); | ||
|
||
const allowCopy = useMemo(() => getMessageEntity(activity)?.keywords.includes('AllowCopy'), [activity]); | ||
|
||
return allowCopy ? ( | ||
<div className={classNames['activity-toolbox']}> | ||
<CopyButton activity={activity} /> | ||
</div> | ||
) : null; | ||
}; | ||
|
||
ActivityToolbox.displayName = 'ActivityToolbox'; | ||
|
||
export default memo(ActivityToolbox); |
Oops, something went wrong.