Skip to content

Commit 469bb1e

Browse files
committed
Fluent: make icons customizable
1 parent 4df27b8 commit 469bb1e

File tree

10 files changed

+118
-82
lines changed

10 files changed

+118
-82
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
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/@babel/[email protected]/babel.min.js"></script>
6+
<script crossorigin="anonymous" src="https://unpkg.com/[email protected]/umd/react.production.min.js"></script>
7+
<script crossorigin="anonymous" src="https://unpkg.com/[email protected]/umd/react-dom.production.min.js"></script>
8+
<script crossorigin="anonymous" src="/test-harness.js"></script>
9+
<script crossorigin="anonymous" src="/test-page-object.js"></script>
10+
<script crossorigin="anonymous" src="/__dist__/webchat-es5.js"></script>
11+
<script crossorigin="anonymous" src="/__dist__/botframework-webchat-fluent-theme.production.min.js"></script>
12+
<style>
13+
#webchat .sendbox__toolbar-button .icon__send {
14+
--monochrome-image-masker__mask-image: url(url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='2 2 22 22'%3E%3Cpath fill='%23000' d='M4 18.5v-13L19.423 12zM5 17l11.85-5L5 7v3.885L9.846 12L5 13.116zm0 0V7z'/%3E%3C/svg%3E"));
15+
}
16+
#webchat .sendbox__toolbar-button .icon__attachment {
17+
--monochrome-image-masker__mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='2 2 22 22'%3E%3Cpath fill='%23000' d='M16.346 11.385V6.769h1v4.616zm-5.538 5.457q-.452-.269-.726-.734q-.274-.466-.274-1.031V6.769h1zM11.96 21q-2.271 0-3.846-1.595t-1.575-3.867v-8.73q0-1.587 1.09-2.697Q8.722 3 10.309 3t2.678 1.11t1.091 2.698V14h-1V6.789q-.006-1.166-.802-1.977T10.308 4q-1.163 0-1.966.821q-.804.821-.804 1.987v8.73q-.005 1.853 1.283 3.157Q10.11 20 11.961 20q.556 0 1.056-.124t.945-.372v1.11q-.468.2-.972.293q-.505.093-1.03.093m4.386-1v-2.616h-2.615v-1h2.615V13.77h1v2.615h2.616v1h-2.616V20z'/%3E%3C/svg%3E");
18+
}
19+
</style>
20+
</head>
21+
<body>
22+
<main id="webchat"></main>
23+
<script type="text/babel">
24+
run(async function () {
25+
const {
26+
React,
27+
ReactDOM: { render },
28+
WebChat: { FluentThemeProvider, ReactWebChat }
29+
} = window; // Imports in UMD fashion.
30+
31+
const { directLine, store } = testHelpers.createDirectLineEmulator();
32+
33+
const App = () => <ReactWebChat directLine={directLine} store={store} />;
34+
35+
render(
36+
<FluentThemeProvider>
37+
<App />
38+
</FluentThemeProvider>,
39+
document.getElementById('webchat')
40+
);
41+
42+
await pageConditions.uiConnected();
43+
44+
await directLine.emulateIncomingActivity(
45+
'Eiusmod anim adipisicing cupidatat adipisicing officia sint qui consequat veniam id aute.'
46+
);
47+
48+
await pageConditions.numActivitiesShown(1);
49+
50+
// THEN: Fluent theme should be loaded.
51+
const buildInfo = Object.fromEntries(
52+
document
53+
.querySelector('head > meta[name="botframework-webchat:fluent-theme"]')
54+
.content.split(';')
55+
.map(value => value.trim().split('='))
56+
);
57+
58+
expect(buildInfo).toHaveProperty('build-tool', 'tsup');
59+
expect(buildInfo).toHaveProperty('module-format', 'esmodules');
60+
expect(buildInfo.version).toMatch(/^\d+\.\d+\.\d+($|-)/u);
61+
62+
expect(window.WebChat.FluentThemeProvider).toBeTruthy();
63+
64+
// THEN: Should render the activity.
65+
await host.snapshot('local');
66+
});
67+
</script>
68+
</body>
69+
</html>
Loading

packages/component/src/Styles/StyleSet/MonochromeImageMasker.ts

+8-3
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,15 @@ export default function createMonochromeImageMaskerStyleSet() {
22
return {
33
'&.webchat__monochrome-image-masker': {
44
backgroundColor: '#707070',
5-
maskImage: 'var(--webchat__monochrome-image-masker__mask-image)',
5+
maskImage: 'var(--monochrome-image-masker__mask-image, var(--webchat__monochrome-image-masker__mask-image))',
66
maskRepeat: 'no-repeat',
7-
WebkitMaskImage: 'var(--webchat__monochrome-image-masker__mask-image)',
8-
WebkitMaskRepeat: 'no-repeat'
7+
maskSize: 'contain',
8+
WebkitMaskImage:
9+
'var(--monochrome-image-masker__mask-image, var(--webchat__monochrome-image-masker__mask-image))',
10+
WebkitMaskRepeat: 'no-repeat',
11+
maskPosition: 'center',
12+
WebkitMaskPosition: 'center',
13+
WebkitMaskSize: 'contain'
914
}
1015
};
1116
}

packages/fluent-theme/src/components/sendBox/Toolbar.module.css

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
justify-content: center;
2222
padding: 3px;
2323

24-
> svg {
24+
> :global(.webchat__monochrome-image-masker) {
2525
font-size: 20px;
2626
pointer-events: none;
2727
}

packages/fluent-theme/src/components/theme/Theme.module.css

+3-1
Original file line numberDiff line numberDiff line change
@@ -591,7 +591,9 @@
591591

592592
/* Monochrome image masker */
593593
:global(.webchat-fluent).theme :global(.webchat__monochrome-image-masker) {
594-
background-color: var(--webchat-colorNeutralForeground4);
594+
background-color: currentColor;
595+
height: 1em;
596+
width: 1em;
595597
}
596598

597599
/* Feedback button */
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,13 @@
1+
import { Components } from 'botframework-webchat-component';
2+
import cx from 'classnames';
13
import React, { memo } from 'react';
24

3-
function AddDocumentIcon(props: Readonly<{ readonly className?: string }>) {
4-
return (
5-
<svg
6-
aria-hidden="true"
7-
className={props.className}
8-
height="1em"
9-
viewBox="0 0 20 20"
10-
width="1em"
11-
xmlns="http://www.w3.org/2000/svg"
12-
>
13-
<path
14-
d="M6 2a2 2 0 0 0-2 2v5.2c.32-.08.66-.15 1-.18V4a1 1 0 0 1 1-1h4v3.5c0 .83.67 1.5 1.5 1.5H15v8a1 1 0 0 1-1 1h-3.6c-.18.36-.4.7-.66 1H14a2 2 0 0 0 2-2V7.41c0-.4-.16-.78-.44-1.06l-3.91-3.91A1.5 1.5 0 0 0 10.59 2H6Zm8.8 5h-3.3a.5.5 0 0 1-.5-.5V3.2L14.8 7ZM10 14.5a4.5 4.5 0 1 1-9 0 4.5 4.5 0 0 1 9 0Zm-4-2a.5.5 0 0 0-1 0V14H3.5a.5.5 0 0 0 0 1H5v1.5a.5.5 0 0 0 1 0V15h1.5a.5.5 0 0 0 0-1H6v-1.5Z"
15-
fill="currentColor"
16-
/>
17-
</svg>
18-
);
5+
const { MonochromeImageMasker } = Components;
6+
7+
const addDocumentIcon = `data:image/svg+xml;utf8,${encodeURIComponent('<svg viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M6 2a2 2 0 0 0-2 2v5.2c.32-.08.66-.15 1-.18V4a1 1 0 0 1 1-1h4v3.5c0 .83.67 1.5 1.5 1.5H15v8a1 1 0 0 1-1 1h-3.6c-.18.36-.4.7-.66 1H14a2 2 0 0 0 2-2V7.41c0-.4-.16-.78-.44-1.06l-3.91-3.91A1.5 1.5 0 0 0 10.59 2H6Zm8.8 5h-3.3a.5.5 0 0 1-.5-.5V3.2L14.8 7ZM10 14.5a4.5 4.5 0 1 1-9 0 4.5 4.5 0 0 1 9 0Zm-4-2a.5.5 0 0 0-1 0V14H3.5a.5.5 0 0 0 0 1H5v1.5a.5.5 0 0 0 1 0V15h1.5a.5.5 0 0 0 0-1H6v-1.5Z"/></svg>')}`;
8+
9+
function AddDocumentIcon(props: Readonly<{ className?: string }>) {
10+
return <MonochromeImageMasker className={cx('icon__add-document', props.className)} src={addDocumentIcon} />;
1911
}
2012

2113
export default memo(AddDocumentIcon);
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,13 @@
1+
import { Components } from 'botframework-webchat-component';
2+
import cx from 'classnames';
13
import React, { memo } from 'react';
24

3-
function AttachmentIcon(props: Readonly<{ readonly className?: string }>) {
4-
return (
5-
<svg
6-
aria-hidden="true"
7-
className={props.className}
8-
height="1em"
9-
viewBox="0 0 20 20"
10-
width="1em"
11-
xmlns="http://www.w3.org/2000/svg"
12-
>
13-
<path
14-
d="m4.83 10.48 5.65-5.65a3 3 0 0 1 4.25 4.24L8 15.8a1.5 1.5 0 0 1-2.12-2.12l6-6.01a.5.5 0 1 0-.7-.71l-6 6.01a2.5 2.5 0 0 0 3.53 3.54l6.71-6.72a4 4 0 1 0-5.65-5.66L4.12 9.78a.5.5 0 0 0 .7.7Z"
15-
fill="currentColor"
16-
/>
17-
</svg>
18-
);
5+
const { MonochromeImageMasker } = Components;
6+
7+
const attachmentIcon = `data:image/svg+xml;utf8,${encodeURIComponent('<svg viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="m4.83 10.48 5.65-5.65a3 3 0 0 1 4.25 4.24L8 15.8a1.5 1.5 0 0 1-2.12-2.12l6-6.01a.5.5 0 1 0-.7-.71l-6 6.01a2.5 2.5 0 0 0 3.53 3.54l6.71-6.72a4 4 0 1 0-5.65-5.66L4.12 9.78a.5.5 0 0 0 .7.7Z"/></svg>')}`;
8+
9+
function AttachmentIcon(props: Readonly<{ className?: string }>) {
10+
return <MonochromeImageMasker className={cx('icon__attachment', props.className)} src={attachmentIcon} />;
1911
}
2012

2113
export default memo(AttachmentIcon);
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,13 @@
1+
import { Components } from 'botframework-webchat-component';
2+
import cx from 'classnames';
13
import React, { memo } from 'react';
24

5+
const { MonochromeImageMasker } = Components;
6+
7+
const infoSmallIcon = `data:image/svg+xml;utf8,${encodeURIComponent('<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M8.5 7.5a.5.5 0 1 0-1 0v3a.5.5 0 0 0 1 0v-3Zm.25-2a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0ZM8 1a7 7 0 1 0 0 14A7 7 0 0 0 8 1ZM2 8a6 6 0 1 1 12 0A6 6 0 0 1 2 8Z"/></svg>')}`;
8+
39
function InfoSmallIcon(props: Readonly<{ readonly className?: string }>) {
4-
return (
5-
<svg
6-
aria-hidden="true"
7-
className={props.className}
8-
height="1em"
9-
viewBox="0 0 16 16"
10-
width="1em"
11-
xmlns="http://www.w3.org/2000/svg"
12-
>
13-
<path
14-
d="M8.5 7.5a.5.5 0 1 0-1 0v3a.5.5 0 0 0 1 0v-3Zm.25-2a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0ZM8 1a7 7 0 1 0 0 14A7 7 0 0 0 8 1ZM2 8a6 6 0 1 1 12 0A6 6 0 0 1 2 8Z"
15-
fill="currentColor"
16-
/>
17-
</svg>
18-
);
10+
return <MonochromeImageMasker className={cx('icon__info--small', props.className)} src={infoSmallIcon} />;
1911
}
2012

2113
export default memo(InfoSmallIcon);
+7-15
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,13 @@
1+
import { Components } from 'botframework-webchat-component';
2+
import cx from 'classnames';
13
import React, { memo } from 'react';
24

5+
const { MonochromeImageMasker } = Components;
6+
7+
const sendIcon = `data:image/svg+xml;utf8,${encodeURIComponent('<svg height="20" viewBox="0 0 20 20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M2.18 2.11a.5.5 0 0 1 .54-.06l15 7.5a.5.5 0 0 1 0 .9l-15 7.5a.5.5 0 0 1-.7-.58L3.98 10 2.02 2.63a.5.5 0 0 1 .16-.52Zm2.7 8.39-1.61 6.06L16.38 10 3.27 3.44 4.88 9.5h6.62a.5.5 0 1 1 0 1H4.88Z" fill="#fff"/></svg>')}`;
8+
39
function SendIcon(props: Readonly<{ readonly className?: string }>) {
4-
return (
5-
<svg
6-
aria-hidden="true"
7-
className={props.className}
8-
height="1em"
9-
viewBox="0 0 20 20"
10-
width="1em"
11-
xmlns="http://www.w3.org/2000/svg"
12-
>
13-
<path
14-
d="M2.18 2.11a.5.5 0 0 1 .54-.06l15 7.5a.5.5 0 0 1 0 .9l-15 7.5a.5.5 0 0 1-.7-.58L3.98 10 2.02 2.63a.5.5 0 0 1 .16-.52Zm2.7 8.39-1.61 6.06L16.38 10 3.27 3.44 4.88 9.5h6.62a.5.5 0 1 1 0 1H4.88Z"
15-
fill="currentColor"
16-
/>
17-
</svg>
18-
);
10+
return <MonochromeImageMasker className={cx('icon__send', props.className)} src={sendIcon} />;
1911
}
2012

2113
export default memo(SendIcon);
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,13 @@
1+
import { Components } from 'botframework-webchat-component';
2+
import cx from 'classnames';
13
import React, { memo } from 'react';
24

5+
const { MonochromeImageMasker } = Components;
6+
7+
const telephoneKeypadIcon = `data:image/svg+xml;utf8,${encodeURIComponent('<svg viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M6 5.25a1.25 1.25 0 1 0 0-2.5 1.25 1.25 0 0 0 0 2.5Zm0 4a1.25 1.25 0 1 0 0-2.5 1.25 1.25 0 0 0 0 2.5ZM7.25 12a1.25 1.25 0 1 1-2.5 0 1.25 1.25 0 0 1 2.5 0ZM10 5.25a1.25 1.25 0 1 0 0-2.5 1.25 1.25 0 0 0 0 2.5ZM11.25 8a1.25 1.25 0 1 1-2.5 0 1.25 1.25 0 0 1 2.5 0ZM10 13.25a1.25 1.25 0 1 0 0-2.5 1.25 1.25 0 0 0 0 2.5ZM11.25 16a1.25 1.25 0 1 1-2.5 0 1.25 1.25 0 0 1 2.5 0ZM14 5.25a1.25 1.25 0 1 0 0-2.5 1.25 1.25 0 0 0 0 2.5ZM15.25 8a1.25 1.25 0 1 1-2.5 0 1.25 1.25 0 0 1 2.5 0ZM14 13.25a1.25 1.25 0 1 0 0-2.5 1.25 1.25 0 0 0 0 2.5Z"/></svg>')}`;
8+
39
function TelephoneKeypadIcon(props: Readonly<{ readonly className?: string }>) {
4-
return (
5-
<svg
6-
aria-hidden="true"
7-
className={props.className}
8-
height="1em"
9-
viewBox="0 0 20 20"
10-
width="1em"
11-
xmlns="http://www.w3.org/2000/svg"
12-
>
13-
<path
14-
d="M6 5.25a1.25 1.25 0 1 0 0-2.5 1.25 1.25 0 0 0 0 2.5Zm0 4a1.25 1.25 0 1 0 0-2.5 1.25 1.25 0 0 0 0 2.5ZM7.25 12a1.25 1.25 0 1 1-2.5 0 1.25 1.25 0 0 1 2.5 0ZM10 5.25a1.25 1.25 0 1 0 0-2.5 1.25 1.25 0 0 0 0 2.5ZM11.25 8a1.25 1.25 0 1 1-2.5 0 1.25 1.25 0 0 1 2.5 0ZM10 13.25a1.25 1.25 0 1 0 0-2.5 1.25 1.25 0 0 0 0 2.5ZM11.25 16a1.25 1.25 0 1 1-2.5 0 1.25 1.25 0 0 1 2.5 0ZM14 5.25a1.25 1.25 0 1 0 0-2.5 1.25 1.25 0 0 0 0 2.5ZM15.25 8a1.25 1.25 0 1 1-2.5 0 1.25 1.25 0 0 1 2.5 0ZM14 13.25a1.25 1.25 0 1 0 0-2.5 1.25 1.25 0 0 0 0 2.5Z"
15-
fill="currentColor"
16-
/>
17-
</svg>
18-
);
10+
return <MonochromeImageMasker className={cx('icon__telephone-keypad', props.className)} src={telephoneKeypadIcon} />;
1911
}
2012

2113
export default memo(TelephoneKeypadIcon);

0 commit comments

Comments
 (0)