Skip to content

Commit aff78e0

Browse files
authored
Add sliding dots typing indicator to Fluent theme (#5447)
* Add sliding dots typing indicator * Update PR number * Add tests * Import test IDs from bundle * Update screenshots * Pause animation * Fix ESLint * Add test for layout * Read test IDs from component * Read test IDs from component * Add useShouldReduceMotion * Add doc * Unformat * Simplify * Add prefers-reduced-motion * Simplify
1 parent cd86ef9 commit aff78e0

27 files changed

+385
-59
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ Notes: web developers are advised to use [`~` (tilde range)](https://github.com/
8383
- Resolved [#2661](https://github.com/microsoft/BotFramework-WebChat/issues/2661) and [#5352](https://github.com/microsoft/BotFramework-WebChat/issues/5352). Added speech recognition continuous mode with barge-in support, in PR [#5426](https://github.com/microsoft/BotFramework-WebChat/pull/5426), by [@RushikeshGavali](https://github.com/RushikeshGavali) and [@compulim](https://github.com/compulim)
8484
- Set `styleOptions.speechRecognitionContinuous` to `true` with a Web Speech API provider with continuous mode support
8585
- Added support of [contentless activity in livestream](https://github.com/microsoft/BotFramework-WebChat/blob/main/docs/LIVESTREAMING.md#scenario-3-interim-activities-with-no-content), in PR [#5430](https://github.com/microsoft/BotFramework-WebChat/pull/5430), by [@compulim](https://github.com/compulim)
86+
- Added sliding dots typing indicator in Fluent theme, in PR [#5447](https://github.com/microsoft/BotFramework-WebChat/pull/5447), by [@compulim](https://github.com/compulim)
8687

8788
### Changed
8889

-71 Bytes
Loading
-173 Bytes
Loading

__tests__/html/fluentTheme/side-by-side.wide.dark.html

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,10 @@
6868
Fluent: { FluentProvider, createDarkTheme }
6969
} = window; // Imports in UMD fashion.
7070

71+
await host.sendDevToolsCommand('Emulation.setEmulatedMedia', {
72+
features: [{ name: 'prefers-reduced-motion', value: 'reduce' }]
73+
});
74+
7175
await host.windowSize(1460, 700, document.getElementById('webchat'));
7276

7377
let timestampStart = new Date(2020, 7, 9).getTime();
@@ -104,17 +108,17 @@
104108
const waveSvg = `data:image/svg+xml;utf8,${encodeURIComponent(`
105109
<svg viewBox="0 50 400 100" xmlns="http://www.w3.org/2000/svg">
106110
<!-- Primary Wave -->
107-
<path d="M0,100 C50,50 100,150 150,100 C200,50 250,150 300,100 C350,50 400,150 400,100"
111+
<path d="M0,100 C50,50 100,150 150,100 C200,50 250,150 300,100 C350,50 400,150 400,100"
108112
stroke="#3B82F6" fill="none" stroke-width="2" opacity="0.5"/>
109-
113+
110114
<!-- Second Harmonic -->
111-
<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"
115+
<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"
112116
stroke="#10B981" fill="none" stroke-width="2" opacity="0.5"/>
113-
117+
114118
<!-- Combined Wave -->
115-
<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"
119+
<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"
116120
stroke="#EF4444" fill="none" stroke-width="3"/>
117-
121+
118122
<!-- Grid Lines -->
119123
<line x1="0" y1="100" x2="400" y2="100" stroke="#CBD5E1" stroke-width="0.5" stroke-dasharray="4"/>
120124
<line x1="100" y1="0" x2="100" y2="200" stroke="#CBD5E1" stroke-width="0.5" stroke-dasharray="4"/>
@@ -576,35 +580,35 @@
576580
"""Create a beautiful visualization of sine waves with different frequencies."""
577581
# Generate time points
578582
t = np.linspace(0, 10, 1000)
579-
583+
580584
# Create waves with different frequencies and phases
581585
wave1 = np.sin(t)
582586
wave2 = 0.5 * np.sin(2 * t + np.pi/4)
583587
wave3 = 0.3 * np.sin(3 * t + np.pi/3)
584-
588+
585589
# Combine waves
586590
combined = wave1 + wave2 + wave3
587-
591+
588592
# Create a stylish plot
589593
plt.style.use('seaborn-darkgrid')
590594
plt.figure(figsize=(12, 8))
591-
595+
592596
# Plot individual waves
593597
plt.plot(t, wave1, label='Primary Wave', alpha=0.5)
594598
plt.plot(t, wave2, label='Second Harmonic', alpha=0.5)
595599
plt.plot(t, wave3, label='Third Harmonic', alpha=0.5)
596-
600+
597601
# Plot combined wave with a thicker line
598-
plt.plot(t, combined, 'r-',
599-
label='Combined Wave',
602+
plt.plot(t, combined, 'r-',
603+
label='Combined Wave',
600604
linewidth=2)
601-
605+
602606
plt.title('Harmonic Wave Composition', fontsize=14)
603607
plt.xlabel('Time', fontsize=12)
604608
plt.ylabel('Amplitude', fontsize=12)
605609
plt.legend()
606610
plt.grid(True, alpha=0.3)
607-
611+
608612
# Show the plot
609613
plt.tight_layout()
610614
plt.show()
@@ -639,7 +643,7 @@
639643
{
640644
"@type": "LikeAction",
641645
actionStatus: "CompletedActionStatus",
642-
target: {
646+
target: {
643647
"@type": "EntryPoint",
644648
urlTemplate: "ms-directline://postback?interaction=like"
645649
}
@@ -681,7 +685,7 @@
681685
{
682686
"@type": "LikeAction",
683687
actionStatus: "PotentialActionStatus",
684-
target: {
688+
target: {
685689
"@type": "EntryPoint",
686690
urlTemplate: "ms-directline://postback?interaction=like"
687691
}

__tests__/html/fluentTheme/side-by-side.wide.html

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,10 @@
7777
WebChat: { FluentThemeProvider, ReactWebChat }
7878
} = window; // Imports in UMD fashion.
7979

80+
await host.sendDevToolsCommand('Emulation.setEmulatedMedia', {
81+
features: [{ name: 'prefers-reduced-motion', value: 'reduce' }]
82+
});
83+
8084
await host.windowSize(1460, 700, document.getElementById('webchat'));
8185

8286
let timestampStart = new Date(2020, 7, 9).getTime();
@@ -114,17 +118,17 @@
114118
const waveSvg = `data:image/svg+xml;utf8,${encodeURIComponent(`
115119
<svg viewBox="0 50 400 100" xmlns="http://www.w3.org/2000/svg">
116120
<!-- Primary Wave -->
117-
<path d="M0,100 C50,50 100,150 150,100 C200,50 250,150 300,100 C350,50 400,150 400,100"
121+
<path d="M0,100 C50,50 100,150 150,100 C200,50 250,150 300,100 C350,50 400,150 400,100"
118122
stroke="#3B82F6" fill="none" stroke-width="2" opacity="0.5"/>
119-
123+
120124
<!-- Second Harmonic -->
121-
<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"
125+
<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"
122126
stroke="#10B981" fill="none" stroke-width="2" opacity="0.5"/>
123-
127+
124128
<!-- Combined Wave -->
125-
<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"
129+
<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"
126130
stroke="#EF4444" fill="none" stroke-width="3"/>
127-
131+
128132
<!-- Grid Lines -->
129133
<line x1="0" y1="100" x2="400" y2="100" stroke="#CBD5E1" stroke-width="0.5" stroke-dasharray="4"/>
130134
<line x1="100" y1="0" x2="100" y2="200" stroke="#CBD5E1" stroke-width="0.5" stroke-dasharray="4"/>
@@ -586,35 +590,35 @@
586590
"""Create a beautiful visualization of sine waves with different frequencies."""
587591
# Generate time points
588592
t = np.linspace(0, 10, 1000)
589-
593+
590594
# Create waves with different frequencies and phases
591595
wave1 = np.sin(t)
592596
wave2 = 0.5 * np.sin(2 * t + np.pi/4)
593597
wave3 = 0.3 * np.sin(3 * t + np.pi/3)
594-
598+
595599
# Combine waves
596600
combined = wave1 + wave2 + wave3
597-
601+
598602
# Create a stylish plot
599603
plt.style.use('seaborn-darkgrid')
600604
plt.figure(figsize=(12, 8))
601-
605+
602606
# Plot individual waves
603607
plt.plot(t, wave1, label='Primary Wave', alpha=0.5)
604608
plt.plot(t, wave2, label='Second Harmonic', alpha=0.5)
605609
plt.plot(t, wave3, label='Third Harmonic', alpha=0.5)
606-
610+
607611
# Plot combined wave with a thicker line
608-
plt.plot(t, combined, 'r-',
609-
label='Combined Wave',
612+
plt.plot(t, combined, 'r-',
613+
label='Combined Wave',
610614
linewidth=2)
611-
615+
612616
plt.title('Harmonic Wave Composition', fontsize=14)
613617
plt.xlabel('Time', fontsize=12)
614618
plt.ylabel('Amplitude', fontsize=12)
615619
plt.legend()
616620
plt.grid(True, alpha=0.3)
617-
621+
618622
# Show the plot
619623
plt.tight_layout()
620624
plt.show()
@@ -649,7 +653,7 @@
649653
{
650654
"@type": "LikeAction",
651655
actionStatus: "CompletedActionStatus",
652-
target: {
656+
target: {
653657
"@type": "EntryPoint",
654658
urlTemplate: "ms-directline://postback?interaction=like"
655659
}
@@ -691,7 +695,7 @@
691695
{
692696
"@type": "LikeAction",
693697
actionStatus: "PotentialActionStatus",
694-
target: {
698+
target: {
695699
"@type": "EntryPoint",
696700
urlTemplate: "ms-directline://postback?interaction=like"
697701
}
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
<!doctype html>
2+
<html lang="en-US">
3+
<head>
4+
<link href="/assets/index.css" rel="stylesheet" type="text/css" />
5+
<script crossorigin="anonymous" src="https://unpkg.com/[email protected]/umd/react.production.min.js"></script>
6+
<script crossorigin="anonymous" src="https://unpkg.com/[email protected]/umd/react-dom.production.min.js"></script>
7+
<script crossorigin="anonymous" src="/test-harness.js"></script>
8+
<script crossorigin="anonymous" src="/test-page-object.js"></script>
9+
<script crossorigin="anonymous" src="/__dist__/webchat-es5.js"></script>
10+
<script crossorigin="anonymous" src="/__dist__/botframework-webchat-fluent-theme.production.min.js"></script>
11+
</head>
12+
<body>
13+
<main id="webchat"></main>
14+
<script type="importmap">
15+
{
16+
"imports": {
17+
"@testduet/wait-for": "https://unpkg.com/@testduet/wait-for@main/dist/wait-for.mjs",
18+
"jest-mock": "https://esm.sh/jest-mock",
19+
"react-dictate-button/internal": "https://unpkg.com/react-dictate-button@main/dist/react-dictate-button.internal.mjs"
20+
}
21+
}
22+
</script>
23+
<script type="module">
24+
import { waitFor } from '@testduet/wait-for';
25+
26+
const isLivestream = new URL(location).searchParams.has('livestream');
27+
28+
run(async function () {
29+
const {
30+
React: { createElement },
31+
ReactDOM: { render },
32+
WebChat: { FluentThemeProvider, ReactWebChat }
33+
} = window; // Imports in UMD fashion.
34+
35+
await host.sendDevToolsCommand('Emulation.setEmulatedMedia', {
36+
features: [{ name: 'prefers-reduced-motion', value: 'reduce' }]
37+
});
38+
39+
const { directLine, store } = testHelpers.createDirectLineEmulator();
40+
41+
render(
42+
createElement(FluentThemeProvider, {}, createElement(ReactWebChat, { directLine, store })),
43+
document.getElementById('webchat')
44+
);
45+
46+
await pageConditions.uiConnected();
47+
48+
// WHEN: Receive a bot message.
49+
await directLine.emulateIncomingActivity({
50+
from: { id: 'u-00001', name: 'Bot', role: 'bot' },
51+
id: 'a-00001',
52+
text: 'Hello, World!',
53+
type: 'message'
54+
});
55+
56+
// WHEN: Bot send either a contentless livestream or typing activity.
57+
await directLine.emulateIncomingActivity({
58+
...(isLivestream
59+
? {
60+
channelData: {
61+
streamSequence: 1,
62+
streamType: 'streaming'
63+
}
64+
}
65+
: {}),
66+
from: { id: 'u-00001', name: 'Bot', role: 'bot' },
67+
id: 'a-00002',
68+
type: 'typing'
69+
});
70+
71+
// THEN: Should show typing indicator.
72+
await waitFor(() => expect(pageElements.typingIndicator()).toBeTruthy());
73+
74+
// THEN: Should match snapshot.
75+
await host.snapshot('local');
76+
77+
// ---
78+
79+
// WHEN: Bot send either a contentless livestream or typing activity.
80+
await directLine.emulateIncomingActivity({
81+
...(isLivestream
82+
? {
83+
channelData: {
84+
streamId: 'a-00002',
85+
streamType: 'final'
86+
}
87+
}
88+
: {
89+
channelData: {
90+
webChat: {
91+
styleOptions: { typingAnimationDuration: 0 }
92+
}
93+
}
94+
}),
95+
from: { id: 'u-00001', name: 'Bot', role: 'bot' },
96+
id: 'a-00002',
97+
type: 'typing'
98+
});
99+
100+
// THEN: Should hide typing indicator.
101+
await waitFor(() => expect(pageElements.typingIndicator()).toBeFalsy());
102+
103+
// THEN: Should match snapshot.
104+
await host.snapshot('local');
105+
});
106+
</script>
107+
</body>
108+
</html>
10.3 KB
Loading
9.32 KB
Loading
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<!doctype html>
2+
<html>
3+
<head>
4+
<script>
5+
location = './typingIndicator?livestream';
6+
</script>
7+
</head>
8+
<body></body>
9+
</html>
10.3 KB
Loading

0 commit comments

Comments
 (0)