botframework-webchat-fluent-theme 4.17.0-main.20240411.647b269 → 4.17.0-main.20240411.ff34969

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/dist/botframework-webchat-fluent-theme.development.js +4081 -0
  2. package/dist/botframework-webchat-fluent-theme.development.js.map +1 -0
  3. package/dist/botframework-webchat-fluent-theme.production.min.js +8 -16
  4. package/dist/botframework-webchat-fluent-theme.production.min.js.map +1 -1
  5. package/dist/index.js +1350 -16
  6. package/dist/index.js.map +1 -1
  7. package/dist/index.mjs.map +1 -1
  8. package/package.json +13 -4
  9. package/src/bundle.ts +9 -2
  10. package/src/components/DropZone.tsx +3 -0
  11. package/src/components/SendBox.tsx +3 -0
  12. package/src/components/SuggestedActions.tsx +3 -0
  13. package/src/components/TelephoneKeypad.tsx +1 -0
  14. package/src/components/Theme.tsx +71 -0
  15. package/src/components/dropZone/index.tsx +132 -0
  16. package/src/components/sendbox/AddAttachmentButton.tsx +77 -0
  17. package/src/components/sendbox/Attachments.tsx +40 -0
  18. package/src/components/sendbox/ErrorMessage.tsx +26 -0
  19. package/src/components/sendbox/TelephoneKeypadToolbarButton.tsx +30 -0
  20. package/src/components/sendbox/TextArea.tsx +146 -0
  21. package/src/components/sendbox/Toolbar.tsx +115 -0
  22. package/src/components/sendbox/index.tsx +234 -0
  23. package/src/components/sendbox/private/useSubmitError.ts +46 -0
  24. package/src/components/sendbox/private/useUniqueId.ts +13 -0
  25. package/src/components/suggestedActions/AccessibleButton.tsx +59 -0
  26. package/src/components/suggestedActions/SuggestedAction.tsx +129 -0
  27. package/src/components/suggestedActions/index.tsx +103 -0
  28. package/src/components/suggestedActions/private/computeSuggestedActionText.ts +21 -0
  29. package/src/components/telephoneKeypad/Provider.tsx +22 -0
  30. package/src/components/telephoneKeypad/Surrogate.tsx +13 -0
  31. package/src/components/telephoneKeypad/index.ts +6 -0
  32. package/src/components/telephoneKeypad/private/Button.tsx +107 -0
  33. package/src/components/telephoneKeypad/private/Context.ts +18 -0
  34. package/src/components/telephoneKeypad/private/TelephoneKeypad.tsx +168 -0
  35. package/src/components/telephoneKeypad/types.ts +1 -0
  36. package/src/components/telephoneKeypad/useShown.ts +9 -0
  37. package/src/external.umd/botframework-webchat-api.ts +3 -0
  38. package/src/external.umd/botframework-webchat-component.ts +4 -0
  39. package/src/external.umd/react.ts +1 -0
  40. package/src/icons/AddDocumentIcon.tsx +20 -0
  41. package/src/icons/AttachmentIcon.tsx +20 -0
  42. package/src/icons/SendIcon.tsx +20 -0
  43. package/src/icons/TelephoneKeypad.tsx +20 -0
  44. package/src/index.ts +2 -1
  45. package/src/private/FluentThemeProvider.tsx +11 -7
  46. package/src/private/useStyleToEmotionObject.ts +32 -0
  47. package/src/styles/index.ts +15 -0
  48. package/src/testIds.ts +21 -0
  49. package/src/types/PropsOf.ts +7 -0
  50. package/src/external/ThemeProvider.tsx +0 -16
  51. package/src/private/SendBox.tsx +0 -7
@@ -0,0 +1,107 @@
1
+ import React, { forwardRef, memo, useCallback, type Ref } from 'react';
2
+
3
+ import { useRefFrom } from 'use-ref-from';
4
+
5
+ import { useStyles } from '../../../styles';
6
+ import { type DTMF } from '../types';
7
+
8
+ const styles = {
9
+ 'webchat__telephone-keypad__button': {
10
+ '-webkit-user-select': 'none',
11
+ alignItems: 'center',
12
+ appearance: 'none',
13
+ // backgroundColor: isDarkTheme() || isHighContrastTheme() ? black : white,
14
+ backgroundColor: 'White',
15
+ borderRadius: '100%',
16
+
17
+ // Whitelabel styles
18
+ // border: `solid 1px ${isHighContrastTheme() ? white : isDarkTheme() ? gray160 : gray40}`,
19
+ // color: 'inherit',
20
+
21
+ border: 'solid 1px var(--webchat-colorNeutralStroke1)',
22
+ color: 'var(--webchat-colorNeutralForeground1)',
23
+ fontWeight: 'var(--webchat-fontWeightSemibold)',
24
+
25
+ cursor: 'pointer',
26
+ display: 'flex',
27
+ flexDirection: 'column',
28
+ height: 60,
29
+ opacity: 0.7,
30
+ padding: 0,
31
+ position: 'relative',
32
+ touchAction: 'none',
33
+ userSelect: 'none',
34
+ width: 60,
35
+
36
+ '&:hover': {
37
+ // backgroundColor: isHighContrastTheme() ? gray210 : isDarkTheme() ? gray150 : gray30
38
+ backgroundColor: 'var(--webchat-colorGray30)'
39
+ }
40
+ },
41
+
42
+ 'webchat__telephone-keypad__button__ruby': {
43
+ // color: isHighContrastTheme() ? white : isDarkTheme() ? gray40 : gray160,
44
+ color: 'var(--webchat-colorGray160)',
45
+ fontSize: 10
46
+ },
47
+
48
+ 'webchat__telephone-keypad__button__text': {
49
+ fontSize: 24,
50
+ marginTop: 8
51
+ },
52
+
53
+ 'webchat__telephone-keypad--horizontal': {
54
+ '& .webchat__telephone-keypad__button': {
55
+ height: 32,
56
+ width: 32,
57
+ margin: '8px 4px',
58
+ justifyContent: 'center'
59
+ },
60
+
61
+ 'webchat__telephone-keypad__button__ruby': {
62
+ display: 'none'
63
+ },
64
+
65
+ '& .webchat__telephone-keypad__button__text': {
66
+ fontSize: 20,
67
+ marginTop: 0
68
+ }
69
+ }
70
+ };
71
+
72
+ type Props = Readonly<{
73
+ button: DTMF;
74
+ ['data-testid']?: string | undefined;
75
+ onClick?: (() => void) | undefined;
76
+ ruby?: string | undefined;
77
+ }>;
78
+
79
+ const Button = memo(
80
+ // As we are all TypeScript, internal components do not need propTypes.
81
+ // eslint-disable-next-line react/prop-types
82
+ forwardRef(({ button, 'data-testid': dataTestId, onClick, ruby }: Props, ref: Ref<HTMLButtonElement>) => {
83
+ const classNames = useStyles(styles);
84
+ const onClickRef = useRefFrom(onClick);
85
+
86
+ const handleClick = useCallback(() => onClickRef.current?.(), [onClickRef]);
87
+
88
+ return (
89
+ <button
90
+ className={classNames['webchat__telephone-keypad__button']}
91
+ data-testid={dataTestId}
92
+ onClick={handleClick}
93
+ ref={ref}
94
+ type="button"
95
+ >
96
+ <span className={classNames['webchat__telephone-keypad__button__text']}>
97
+ {button === 'star' ? '\u2217' : button === 'pound' ? '#' : button}
98
+ </span>
99
+ {!!ruby && <ruby className={classNames['webchat__telephone-keypad__button__ruby']}>{ruby}</ruby>}
100
+ </button>
101
+ );
102
+ })
103
+ );
104
+
105
+ Button.displayName = 'TelephoneKeypad.Button';
106
+
107
+ export default Button;
@@ -0,0 +1,18 @@
1
+ import { createContext, type Dispatch, type SetStateAction } from 'react';
2
+
3
+ type ContextType = Readonly<{
4
+ setShown: Dispatch<SetStateAction<boolean>>;
5
+ shown: boolean;
6
+ }>;
7
+
8
+ const Context = createContext<ContextType>(
9
+ new Proxy({} as ContextType, {
10
+ get() {
11
+ throw new Error('botframework-webchat: This hook can only used under its corresponding <Provider>.');
12
+ }
13
+ })
14
+ );
15
+
16
+ Context.displayName = 'TelephoneKeypad.Context';
17
+
18
+ export default Context;
@@ -0,0 +1,168 @@
1
+ import React, { KeyboardEventHandler, memo, useCallback, useEffect, useRef, type ReactNode } from 'react';
2
+ import { useRefFrom } from 'use-ref-from';
3
+
4
+ import { useStyles } from '../../../styles';
5
+ import Button from './Button';
6
+ // import HorizontalDialPadController from './HorizontalDialPadController';
7
+ import testIds from '../../../testIds';
8
+ import { type DTMF } from '../types';
9
+ import useShown from '../useShown';
10
+
11
+ const styles = {
12
+ 'webchat__telephone-keypad': {
13
+ /* Commented out whitelabel styles for now. */
14
+ // background: getHighContrastDarkThemeColor(highContrastColor: black, darkThemeColor: gray190, string, defaultColor: gray10),
15
+ // borderRadius: '8px 8px 0px 0px',
16
+ // boxShadow: '-3px 0px 7px 0px rgba(0, 0, 0, 0.13), -0.6px 0px 1.8px 0px rgba(0, 0, 0, 0.10)',
17
+
18
+ alignItems: 'center',
19
+ background: 'var(--webchat-colorNeutralBackground1)',
20
+ // border: isHighContrastTheme() ? `1px solid ${white}` : 'none',
21
+ border: 'none',
22
+ borderRadius: 'var(--webchat-borderRadiusXLarge)',
23
+ // boxShadow: 'var(--shadow16)',
24
+ display: 'flex',
25
+ flexDirection: 'column',
26
+ fontFamily: 'var(--webchat-fontFamilyBase)',
27
+ justifyContent: 'center'
28
+ // margin: 'var(--spacingHorizontalMNudge)'
29
+ },
30
+
31
+ 'webchat__telephone-keypad__box': {
32
+ boxSizing: 'border-box',
33
+ display: 'grid',
34
+ gap: '16px',
35
+ gridTemplateColumns: 'repeat(3, 1fr)',
36
+ gridTemplateRows: 'repeat(4, 1fr)',
37
+ justifyItems: 'center',
38
+ padding: '16px',
39
+ width: '100%'
40
+ }
41
+ };
42
+
43
+ type Props = Readonly<{
44
+ autoFocus?: boolean | undefined;
45
+ isHorizontal: boolean;
46
+ onButtonClick: (button: DTMF) => void;
47
+ }>;
48
+
49
+ const Orientation = memo(
50
+ ({ children, isHorizontal }: Readonly<{ children?: ReactNode | undefined; isHorizontal: boolean }>) => {
51
+ const classNames = useStyles(styles);
52
+
53
+ return isHorizontal ? (
54
+ // <HorizontalDialPadController>{children}</HorizontalDialPadController>
55
+ false
56
+ ) : (
57
+ <div className={classNames['webchat__telephone-keypad__box']}>{children}</div>
58
+ );
59
+ }
60
+ );
61
+
62
+ Orientation.displayName = 'TelephoneKeypad:Orientation';
63
+
64
+ const TelephoneKeypad = memo(({ autoFocus, onButtonClick, isHorizontal }: Props) => {
65
+ const autoFocusRef = useRefFrom(autoFocus);
66
+ const classNames = useStyles(styles);
67
+ const firstButtonRef = useRef<HTMLButtonElement>(null);
68
+ const onButtonClickRef = useRefFrom(onButtonClick);
69
+ const [, setShown] = useShown();
70
+
71
+ const handleButton1Click = useCallback(() => onButtonClickRef.current?.('1'), [onButtonClickRef]);
72
+ const handleButton2Click = useCallback(() => onButtonClickRef.current?.('2'), [onButtonClickRef]);
73
+ const handleButton3Click = useCallback(() => onButtonClickRef.current?.('3'), [onButtonClickRef]);
74
+ const handleButton4Click = useCallback(() => onButtonClickRef.current?.('4'), [onButtonClickRef]);
75
+ const handleButton5Click = useCallback(() => onButtonClickRef.current?.('5'), [onButtonClickRef]);
76
+ const handleButton6Click = useCallback(() => onButtonClickRef.current?.('6'), [onButtonClickRef]);
77
+ const handleButton7Click = useCallback(() => onButtonClickRef.current?.('7'), [onButtonClickRef]);
78
+ const handleButton8Click = useCallback(() => onButtonClickRef.current?.('8'), [onButtonClickRef]);
79
+ const handleButton9Click = useCallback(() => onButtonClickRef.current?.('9'), [onButtonClickRef]);
80
+ const handleButton0Click = useCallback(() => onButtonClickRef.current?.('0'), [onButtonClickRef]);
81
+ const handleButtonStarClick = useCallback(() => onButtonClickRef.current?.('star'), [onButtonClickRef]);
82
+ const handleButtonPoundClick = useCallback(() => onButtonClickRef.current?.('pound'), [onButtonClickRef]);
83
+ const handleKeyDown = useCallback<KeyboardEventHandler<HTMLDivElement>>(
84
+ event => {
85
+ if (event.key === 'Escape') {
86
+ // TODO: Should send focus to the send box.
87
+ setShown(false);
88
+ }
89
+ },
90
+ [setShown]
91
+ );
92
+
93
+ useEffect(() => {
94
+ autoFocusRef.current && firstButtonRef.current?.focus();
95
+ }, [autoFocusRef, firstButtonRef]);
96
+
97
+ return (
98
+ <div className={classNames['webchat__telephone-keypad']} onKeyDown={handleKeyDown}>
99
+ <Orientation isHorizontal={isHorizontal}>
100
+ <Button
101
+ button="1"
102
+ data-testid={testIds.sendBoxTelephoneKeypadButton1}
103
+ onClick={handleButton1Click}
104
+ ref={firstButtonRef}
105
+ />
106
+ <Button
107
+ button="2"
108
+ data-testid={testIds.sendBoxTelephoneKeypadButton2}
109
+ onClick={handleButton2Click}
110
+ ruby="ABC"
111
+ />
112
+ <Button
113
+ button="3"
114
+ data-testid={testIds.sendBoxTelephoneKeypadButton3}
115
+ onClick={handleButton3Click}
116
+ ruby="DEF"
117
+ />
118
+ <Button
119
+ button="4"
120
+ data-testid={testIds.sendBoxTelephoneKeypadButton4}
121
+ onClick={handleButton4Click}
122
+ ruby="GHI"
123
+ />
124
+ <Button
125
+ button="5"
126
+ data-testid={testIds.sendBoxTelephoneKeypadButton5}
127
+ onClick={handleButton5Click}
128
+ ruby="JKL"
129
+ />
130
+ <Button
131
+ button="6"
132
+ data-testid={testIds.sendBoxTelephoneKeypadButton6}
133
+ onClick={handleButton6Click}
134
+ ruby="MNO"
135
+ />
136
+ <Button
137
+ button="7"
138
+ data-testid={testIds.sendBoxTelephoneKeypadButton7}
139
+ onClick={handleButton7Click}
140
+ ruby="PQRS"
141
+ />
142
+ <Button
143
+ button="8"
144
+ data-testid={testIds.sendBoxTelephoneKeypadButton8}
145
+ onClick={handleButton8Click}
146
+ ruby="TUV"
147
+ />
148
+ <Button
149
+ button="9"
150
+ data-testid={testIds.sendBoxTelephoneKeypadButton9}
151
+ onClick={handleButton9Click}
152
+ ruby="WXYZ"
153
+ />
154
+ <Button button="star" data-testid={testIds.sendBoxTelephoneKeypadButtonStar} onClick={handleButtonStarClick} />
155
+ <Button button="0" data-testid={testIds.sendBoxTelephoneKeypadButton0} onClick={handleButton0Click} ruby="+" />
156
+ <Button
157
+ button="pound"
158
+ data-testid={testIds.sendBoxTelephoneKeypadButtonPound}
159
+ onClick={handleButtonPoundClick}
160
+ />
161
+ </Orientation>
162
+ </div>
163
+ );
164
+ });
165
+
166
+ TelephoneKeypad.displayName = 'TelephoneKeypad';
167
+
168
+ export default TelephoneKeypad;
@@ -0,0 +1 @@
1
+ export type DTMF = '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | '0' | 'star' | 'pound';
@@ -0,0 +1,9 @@
1
+ import { useContext, useMemo, type Dispatch, type SetStateAction } from 'react';
2
+
3
+ import Context from './private/Context';
4
+
5
+ export default function useShown(): readonly [boolean, Dispatch<SetStateAction<boolean>>] {
6
+ const { setShown, shown } = useContext(Context);
7
+
8
+ return useMemo(() => Object.freeze([shown, setShown]), [shown, setShown]);
9
+ }
@@ -0,0 +1,3 @@
1
+ module.exports = {
2
+ hooks: (globalThis as any).WebChat.hooks
3
+ };
@@ -0,0 +1,4 @@
1
+ module.exports = {
2
+ Components: (globalThis as any).WebChat.Components,
3
+ hooks: (globalThis as any).WebChat.hooks
4
+ };
@@ -0,0 +1 @@
1
+ module.exports = (globalThis as any).React;
@@ -0,0 +1,20 @@
1
+ import React from 'react';
2
+
3
+ export function AddDocumentIcon(props: Readonly<{ readonly className?: string }>) {
4
+ return (
5
+ <svg
6
+ aria-hidden="true"
7
+ className={props.className}
8
+ fill="currentColor"
9
+ height="1em"
10
+ viewBox="0 0 20 20"
11
+ width="1em"
12
+ xmlns="http://www.w3.org/2000/svg"
13
+ >
14
+ <path
15
+ 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"
16
+ fill="currentColor"
17
+ />
18
+ </svg>
19
+ );
20
+ }
@@ -0,0 +1,20 @@
1
+ import React from 'react';
2
+
3
+ export function AttachmentIcon(props: Readonly<{ readonly className?: string }>) {
4
+ return (
5
+ <svg
6
+ aria-hidden="true"
7
+ className={props.className}
8
+ fill="currentColor"
9
+ height="1em"
10
+ viewBox="0 0 20 20"
11
+ width="1em"
12
+ xmlns="http://www.w3.org/2000/svg"
13
+ >
14
+ <path
15
+ 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"
16
+ fill="currentColor"
17
+ />
18
+ </svg>
19
+ );
20
+ }
@@ -0,0 +1,20 @@
1
+ import React from 'react';
2
+
3
+ export function SendIcon(props: Readonly<{ readonly className?: string }>) {
4
+ return (
5
+ <svg
6
+ aria-hidden="true"
7
+ className={props.className}
8
+ fill="currentColor"
9
+ height="1em"
10
+ viewBox="0 0 20 20"
11
+ width="1em"
12
+ xmlns="http://www.w3.org/2000/svg"
13
+ >
14
+ <path
15
+ 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"
16
+ fill="currentColor"
17
+ />
18
+ </svg>
19
+ );
20
+ }
@@ -0,0 +1,20 @@
1
+ import React from 'react';
2
+
3
+ export function TelephoneKeypadIcon(props: Readonly<{ readonly className?: string }>) {
4
+ return (
5
+ <svg
6
+ aria-hidden="true"
7
+ className={props.className}
8
+ fill="currentColor"
9
+ height="1em"
10
+ viewBox="0 0 20 20"
11
+ width="1em"
12
+ xmlns="http://www.w3.org/2000/svg"
13
+ >
14
+ <path
15
+ 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"
16
+ fill="currentColor"
17
+ />
18
+ </svg>
19
+ );
20
+ }
package/src/index.ts CHANGED
@@ -1,9 +1,10 @@
1
1
  import { injectMetaTag } from 'inject-meta-tag';
2
2
 
3
3
  import FluentThemeProvider from './private/FluentThemeProvider';
4
+ import testIds from './testIds';
4
5
 
5
6
  declare const NPM_PACKAGE_VERSION: string;
6
7
 
7
8
  injectMetaTag('botframework-webchat-fluent-theme:version', NPM_PACKAGE_VERSION);
8
9
 
9
- export { FluentThemeProvider };
10
+ export { FluentThemeProvider, testIds };
@@ -1,18 +1,22 @@
1
+ import { Components } from 'botframework-webchat-component';
1
2
  import React, { memo, type ReactNode } from 'react';
2
3
 
3
- import ThemeProvider from '../external/ThemeProvider';
4
- import SendBox from './SendBox';
4
+ import { TelephoneKeypadProvider } from '../components/TelephoneKeypad';
5
+ import WebChatTheme from '../components/Theme';
6
+ import SendBox from '../components/SendBox';
5
7
 
6
- type Props = Readonly<{ children?: ReactNode | undefined }>;
8
+ const { ThemeProvider } = Components;
7
9
 
8
- const STYLE_OPTIONS = { bubbleBackground: '#fee' };
10
+ type Props = Readonly<{ children?: ReactNode | undefined }>;
9
11
 
10
12
  const sendBoxMiddleware = [() => () => () => SendBox];
11
13
 
12
14
  const FluentThemeProvider = ({ children }: Props) => (
13
- <ThemeProvider sendBoxMiddleware={sendBoxMiddleware} styleOptions={STYLE_OPTIONS}>
14
- {children}
15
- </ThemeProvider>
15
+ <WebChatTheme>
16
+ <TelephoneKeypadProvider>
17
+ <ThemeProvider sendBoxMiddleware={sendBoxMiddleware}>{children}</ThemeProvider>
18
+ </TelephoneKeypadProvider>
19
+ </WebChatTheme>
16
20
  );
17
21
 
18
22
  export default memo(FluentThemeProvider);
@@ -0,0 +1,32 @@
1
+ import createEmotion, { type Emotion } from '@emotion/css/create-instance';
2
+ // TODO: [P0] Use `math-random`. Today, it requires "crypto" today, maybe missing "conditions" or what.
3
+ // import random from 'math-random';
4
+ import { useMemo } from 'react';
5
+
6
+ const { random } = Math;
7
+
8
+ const emotionPool: Record<string, Emotion> = {};
9
+ const nonce = '';
10
+
11
+ /* eslint no-magic-numbers: ["error", { "ignore": [0, 2, 5, 26, 65] }] */
12
+
13
+ function createCSSKey() {
14
+ return random()
15
+ .toString(26)
16
+ .substr(2, 5)
17
+ .replace(/\d/gu, (value: string) => String.fromCharCode(value.charCodeAt(0) + 65));
18
+ }
19
+
20
+ export default function useStyleToEmotionObject() {
21
+ return useMemo(() => {
22
+ // Emotion doesn't hash with nonce. We need to provide the pooling mechanism.
23
+ // 1. If 2 instances use different nonce, they should result in different hash;
24
+ // 2. If 2 instances are being mounted, pooling will make sure we render only 1 set of <style> tags, instead of 2.
25
+ const emotion =
26
+ // Prefix "id-" to prevent object injection attack.
27
+ emotionPool[`id-${nonce}`] ||
28
+ (emotionPool[`id-${nonce}`] = createEmotion({ key: `webchat--css-${createCSSKey()}`, nonce }));
29
+
30
+ return (style: TemplateStringsArray) => emotion.css(style);
31
+ }, []);
32
+ }
@@ -0,0 +1,15 @@
1
+ import { useMemo } from 'react';
2
+
3
+ import useStyleToEmotionObject from '../private/useStyleToEmotionObject';
4
+
5
+ export function useStyles<T extends Record<`webchat-fluent__${string}`, any>>(styles: T): Record<keyof T, string> {
6
+ const getClassName = useStyleToEmotionObject();
7
+ // @ts-expect-error: entries/fromEntries don't allow to specify keys type
8
+ return useMemo(
9
+ () =>
10
+ Object.freeze(
11
+ Object.fromEntries(Object.entries(styles).map(([cls, style]) => [cls, `${cls} ${getClassName(style)}`]))
12
+ ),
13
+ [styles, getClassName]
14
+ );
15
+ }
package/src/testIds.ts ADDED
@@ -0,0 +1,21 @@
1
+ const testIds = {
2
+ sendBoxDropZone: 'send box drop zone',
3
+ sendBoxSendButton: 'send box send button',
4
+ sendBoxTextBox: 'send box text area',
5
+ sendBoxTelephoneKeypadButton1: `send box telephone keypad button 1`,
6
+ sendBoxTelephoneKeypadButton2: `send box telephone keypad button 2`,
7
+ sendBoxTelephoneKeypadButton3: `send box telephone keypad button 3`,
8
+ sendBoxTelephoneKeypadButton4: `send box telephone keypad button 4`,
9
+ sendBoxTelephoneKeypadButton5: `send box telephone keypad button 5`,
10
+ sendBoxTelephoneKeypadButton6: `send box telephone keypad button 6`,
11
+ sendBoxTelephoneKeypadButton7: `send box telephone keypad button 7`,
12
+ sendBoxTelephoneKeypadButton8: `send box telephone keypad button 8`,
13
+ sendBoxTelephoneKeypadButton9: `send box telephone keypad button 9`,
14
+ sendBoxTelephoneKeypadButton0: `send box telephone keypad button 0`,
15
+ sendBoxTelephoneKeypadButtonStar: `send box telephone keypad button star`,
16
+ sendBoxTelephoneKeypadButtonPound: `send box telephone keypad button pound`,
17
+ sendBoxTelephoneKeypadToolbarButton: 'send box telephone keypad toolbar button',
18
+ sendBoxUploadButton: 'send box upload button'
19
+ };
20
+
21
+ export default testIds;
@@ -0,0 +1,7 @@
1
+ import { type ComponentType, type MemoExoticComponent } from 'react';
2
+
3
+ export type PropsOf<T> = T extends ComponentType<infer P>
4
+ ? P
5
+ : T extends MemoExoticComponent<ComponentType<infer P>>
6
+ ? P
7
+ : never;
@@ -1,16 +0,0 @@
1
- import { type Components } from 'botframework-webchat-component';
2
-
3
- // TODO: We should do isomorphic:
4
- // - If loading UMD, we should look at window.WebChat.Components.ThemeProvider
5
- // - Otherwise, we should import { type Components } from 'botframework-webchat-component'
6
- type ThemeProviderType = (typeof Components)['ThemeProvider'];
7
-
8
- const {
9
- WebChat: {
10
- Components: { ThemeProvider }
11
- }
12
- } = globalThis as unknown as {
13
- WebChat: { Components: { ThemeProvider: ThemeProviderType } };
14
- };
15
-
16
- export default ThemeProvider;
@@ -1,7 +0,0 @@
1
- import React, { memo } from 'react';
2
-
3
- type Props = Readonly<{ className?: string | undefined }>;
4
-
5
- const SendBox = ({ className }: Props) => <div className={className}>{'Fluent send box'}</div>;
6
-
7
- export default memo(SendBox);