botframework-webchat-fluent-theme 4.17.1 → 4.18.1-hotfix.20260127.b53acdf
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.
- package/dist/botframework-webchat-fluent-theme.css.map +1 -1
- package/dist/botframework-webchat-fluent-theme.d.mts +23 -5
- package/dist/botframework-webchat-fluent-theme.d.ts +23 -5
- package/dist/botframework-webchat-fluent-theme.development.css.map +1 -1
- package/dist/botframework-webchat-fluent-theme.development.js +11 -1
- package/dist/botframework-webchat-fluent-theme.development.js.map +1 -1
- package/dist/botframework-webchat-fluent-theme.js +1 -1
- package/dist/botframework-webchat-fluent-theme.js.map +1 -1
- package/dist/botframework-webchat-fluent-theme.mjs +1 -1
- package/dist/botframework-webchat-fluent-theme.mjs.map +1 -1
- package/dist/botframework-webchat-fluent-theme.production.min.css.map +1 -1
- package/dist/botframework-webchat-fluent-theme.production.min.js +11 -1
- package/dist/botframework-webchat-fluent-theme.production.min.js.map +1 -1
- package/package.json +27 -26
- package/src/bundle.ts +2 -0
- package/src/components/activity/ActivityDecorator.module.css +432 -0
- package/src/components/activity/ActivityDecorator.tsx +26 -0
- package/src/components/activity/ActivityLoader.module.css +10 -0
- package/src/components/activity/ActivityLoader.tsx +21 -0
- package/src/components/activity/CopilotMessageHeader.module.css +38 -0
- package/src/components/activity/CopilotMessageHeader.tsx +49 -0
- package/src/components/activity/index.ts +2 -0
- package/src/components/activity/private/isAIGeneratedActivity.ts +5 -0
- package/src/components/activity/private/useActivityAuthor.ts +16 -0
- package/src/components/activity/private/useActivityStyleOptions.ts +19 -0
- package/src/components/assets/AssetComposer.tsx +37 -0
- package/src/components/assets/AssetName.ts +1 -0
- package/src/components/assets/SlidingDots.tsx +61 -0
- package/src/components/assets/private/Context.ts +24 -0
- package/src/components/assets/private/useAssetURL.ts +12 -0
- package/src/components/assets/private/useContext.ts +8 -0
- package/src/components/dropZone/DropZone.module.css +3 -4
- package/src/components/dropZone/DropZone.tsx +41 -6
- package/src/components/linerActivity/index.ts +2 -0
- package/src/components/linerActivity/private/LinerActivity.tsx +20 -0
- package/src/components/linerActivity/private/LinerMessageActivity.module.css +28 -0
- package/src/components/linerActivity/private/isLinerMessageActivity.ts +7 -0
- package/src/components/preChatActivity/PreChatMessageActivity.module.css +56 -0
- package/src/components/preChatActivity/PreChatMessageActivity.tsx +60 -0
- package/src/components/preChatActivity/StarterPromptsCardAction.module.css +126 -0
- package/src/components/preChatActivity/StarterPromptsCardAction.tsx +75 -0
- package/src/components/preChatActivity/StarterPromptsToolbar.module.css +18 -0
- package/src/components/preChatActivity/StarterPromptsToolbar.tsx +47 -0
- package/src/components/preChatActivity/index.tsx +2 -0
- package/src/components/preChatActivity/isPreChatMessageActivity.ts +7 -0
- package/src/components/sendBox/Attachments.module.css +1 -1
- package/src/components/sendBox/Attachments.tsx +5 -4
- package/src/components/sendBox/ErrorMessage.tsx +15 -4
- package/src/components/sendBox/SendBox.module.css +21 -6
- package/src/components/sendBox/SendBox.tsx +95 -56
- package/src/components/sendBox/TextArea.module.css +27 -8
- package/src/components/sendBox/TextArea.tsx +60 -31
- package/src/components/sendBox/Toolbar.module.css +15 -7
- package/src/components/sendBox/Toolbar.tsx +17 -7
- package/src/components/sendBox/index.tsx +1 -1
- package/src/components/sendBox/private/useSubmitError.ts +17 -4
- package/src/components/sendBox/private/useTranscriptNavigation.ts +53 -0
- package/src/components/suggestedActions/AccessibleButton.tsx +15 -13
- package/src/components/suggestedActions/SuggestedAction.module.css +14 -13
- package/src/components/suggestedActions/SuggestedAction.tsx +7 -4
- package/src/components/suggestedActions/SuggestedActions.module.css +2 -3
- package/src/components/suggestedActions/SuggestedActions.tsx +49 -46
- package/src/components/telephoneKeypad/private/Button.module.css +2 -3
- package/src/components/telephoneKeypad/private/Button.tsx +1 -5
- package/src/components/telephoneKeypad/private/TelephoneKeypad.module.css +0 -1
- package/src/components/telephoneKeypad/private/TelephoneKeypad.tsx +4 -8
- package/src/components/telephoneKeypad/types.ts +1 -1
- package/src/components/theme/Theme.module.css +665 -15
- package/src/components/theme/Theme.tsx +4 -3
- package/src/components/typingIndicator/SlidingDotsTypingIndicator.module.css +12 -0
- package/src/components/typingIndicator/SlidingDotsTypingIndicator.tsx +19 -0
- package/src/env.d.ts +1 -7
- package/src/external.umd/botframework-webchat-api/decorator.ts +1 -0
- package/src/external.umd/botframework-webchat-component/decorator.ts +1 -0
- package/src/external.umd/botframework-webchat-component/index.ts +5 -0
- package/src/external.umd/botframework-webchat-component/internal.ts +1 -0
- package/src/icons/AddDocumentIcon.tsx +8 -16
- package/src/icons/AttachmentIcon.tsx +8 -16
- package/src/icons/InfoSmallIcon.tsx +7 -15
- package/src/icons/SendIcon.tsx +7 -15
- package/src/icons/TelephoneKeypadIcon.tsx +7 -15
- package/src/index.ts +2 -4
- package/src/private/FluentThemeProvider.tsx +91 -10
- package/src/private/VariantComposer.ts +29 -0
- package/src/private/createComposer.tsx +16 -0
- package/src/private/useVariants.ts +7 -0
- package/src/styles/createStyles.ts +5 -0
- package/src/styles/index.ts +3 -2
- package/src/styles/useStyles.ts +2 -19
- package/src/styles/useVariantClassName.ts +16 -0
- package/src/testIds.ts +3 -0
- package/src/tsconfig.json +12 -10
- package/src/types/PropsOf.ts +2 -5
- package/src/external.umd/botframework-webchat-component.ts +0 -4
- package/src/styles/injectStyle.ts +0 -9
- /package/src/external.umd/{botframework-webchat-api.ts → botframework-webchat-api/index.ts} +0 -0
|
@@ -1,13 +1,26 @@
|
|
|
1
|
+
import { hooks } from 'botframework-webchat-api';
|
|
1
2
|
import cx from 'classnames';
|
|
2
|
-
import React, {
|
|
3
|
+
import React, {
|
|
4
|
+
forwardRef,
|
|
5
|
+
Fragment,
|
|
6
|
+
useCallback,
|
|
7
|
+
useRef,
|
|
8
|
+
type FormEventHandler,
|
|
9
|
+
type KeyboardEventHandler,
|
|
10
|
+
type MouseEventHandler,
|
|
11
|
+
type ReactNode
|
|
12
|
+
} from 'react';
|
|
3
13
|
import { useStyles } from '../../styles';
|
|
4
14
|
import styles from './TextArea.module.css';
|
|
5
15
|
|
|
16
|
+
const { useUIState } = hooks;
|
|
17
|
+
|
|
6
18
|
const TextArea = forwardRef<
|
|
7
19
|
HTMLTextAreaElement,
|
|
8
20
|
Readonly<{
|
|
9
21
|
'aria-label'?: string | undefined;
|
|
10
22
|
className?: string | undefined;
|
|
23
|
+
completion?: ReactNode | undefined;
|
|
11
24
|
'data-testid'?: string | undefined;
|
|
12
25
|
|
|
13
26
|
/**
|
|
@@ -19,18 +32,31 @@ const TextArea = forwardRef<
|
|
|
19
32
|
* This ensures the flow of focus did not sent to document body
|
|
20
33
|
*/
|
|
21
34
|
hidden?: boolean | undefined;
|
|
35
|
+
onClick?: MouseEventHandler<HTMLTextAreaElement> | undefined;
|
|
22
36
|
onInput?: FormEventHandler<HTMLTextAreaElement> | undefined;
|
|
23
37
|
placeholder?: string | undefined;
|
|
24
38
|
startRows?: number | undefined;
|
|
25
39
|
value?: string | undefined;
|
|
26
40
|
}>
|
|
27
41
|
>((props, ref) => {
|
|
42
|
+
const [uiState] = useUIState();
|
|
28
43
|
const classNames = useStyles(styles);
|
|
44
|
+
const isInCompositionRef = useRef<boolean>(false);
|
|
45
|
+
|
|
46
|
+
const disabled = uiState === 'disabled';
|
|
47
|
+
|
|
48
|
+
const handleCompositionEnd = useCallback(() => {
|
|
49
|
+
isInCompositionRef.current = false;
|
|
50
|
+
}, [isInCompositionRef]);
|
|
51
|
+
|
|
52
|
+
const handleCompositionStart = useCallback(() => {
|
|
53
|
+
isInCompositionRef.current = true;
|
|
54
|
+
}, [isInCompositionRef]);
|
|
29
55
|
|
|
30
56
|
const handleKeyDown = useCallback<KeyboardEventHandler<HTMLTextAreaElement>>(event => {
|
|
31
57
|
// Shift+Enter adds a new line
|
|
32
58
|
// Enter requests related form submission
|
|
33
|
-
if (!event.shiftKey && event.key === 'Enter') {
|
|
59
|
+
if (!event.shiftKey && event.key === 'Enter' && !isInCompositionRef.current) {
|
|
34
60
|
event.preventDefault();
|
|
35
61
|
|
|
36
62
|
if ('form' in event.target && event.target.form instanceof HTMLFormElement) {
|
|
@@ -43,39 +69,42 @@ const TextArea = forwardRef<
|
|
|
43
69
|
<div
|
|
44
70
|
className={cx(
|
|
45
71
|
classNames['sendbox__text-area'],
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
},
|
|
72
|
+
classNames['sendbox__text-area--scroll'],
|
|
73
|
+
{ [classNames['sendbox__text-area--hidden']]: props.hidden },
|
|
74
|
+
{ [classNames['sendbox__text-area--in-completion']]: props.completion },
|
|
49
75
|
props.className
|
|
50
76
|
)}
|
|
51
77
|
role={props.hidden ? 'hidden' : undefined}
|
|
52
78
|
>
|
|
53
|
-
|
|
54
|
-
className={cx(
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
+
{uiState === 'blueprint' ? (
|
|
80
|
+
<div className={cx(classNames['sendbox__text-area-doppelganger'], classNames['sendbox__text-area-shared'])}>
|
|
81
|
+
{' '}
|
|
82
|
+
</div>
|
|
83
|
+
) : (
|
|
84
|
+
<Fragment>
|
|
85
|
+
<div className={cx(classNames['sendbox__text-area-doppelganger'], classNames['sendbox__text-area-shared'])}>
|
|
86
|
+
{props.completion ? props.completion : props.value || props.placeholder}{' '}
|
|
87
|
+
</div>
|
|
88
|
+
<textarea
|
|
89
|
+
aria-disabled={disabled}
|
|
90
|
+
aria-label={props['aria-label']}
|
|
91
|
+
className={cx(classNames['sendbox__text-area-input'], classNames['sendbox__text-area-shared'])}
|
|
92
|
+
data-testid={props['data-testid']}
|
|
93
|
+
onClick={props.onClick}
|
|
94
|
+
onCompositionEnd={handleCompositionEnd}
|
|
95
|
+
onCompositionStart={handleCompositionStart}
|
|
96
|
+
onInput={props.onInput}
|
|
97
|
+
onKeyDown={handleKeyDown}
|
|
98
|
+
placeholder={props.placeholder}
|
|
99
|
+
readOnly={disabled}
|
|
100
|
+
ref={ref}
|
|
101
|
+
rows={props.startRows ?? 1}
|
|
102
|
+
// eslint-disable-next-line no-magic-numbers
|
|
103
|
+
tabIndex={props.hidden ? -1 : undefined}
|
|
104
|
+
value={props.value}
|
|
105
|
+
/>
|
|
106
|
+
</Fragment>
|
|
107
|
+
)}
|
|
79
108
|
</div>
|
|
80
109
|
);
|
|
81
110
|
});
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
:global(.webchat-fluent) .sendbox__toolbar {
|
|
2
|
+
--webchat__sendbox-button-height: 32px;
|
|
3
|
+
|
|
2
4
|
display: flex;
|
|
3
5
|
gap: 4px;
|
|
6
|
+
height: var(--webchat__sendbox-button-height);
|
|
4
7
|
margin-inline-start: auto;
|
|
5
8
|
}
|
|
6
9
|
|
|
@@ -14,11 +17,11 @@
|
|
|
14
17
|
color: currentColor;
|
|
15
18
|
cursor: pointer;
|
|
16
19
|
display: flex;
|
|
20
|
+
height: var(--webchat__sendbox-button-height);
|
|
17
21
|
justify-content: center;
|
|
18
22
|
padding: 3px;
|
|
19
|
-
width: 32px;
|
|
20
23
|
|
|
21
|
-
>
|
|
24
|
+
> :global(.webchat__monochrome-image-masker) {
|
|
22
25
|
font-size: 20px;
|
|
23
26
|
pointer-events: none;
|
|
24
27
|
}
|
|
@@ -26,15 +29,18 @@
|
|
|
26
29
|
&.sendbox__toolbar-button--selected {
|
|
27
30
|
color: var(--webchat-colorNeutralForeground2BrandSelected);
|
|
28
31
|
}
|
|
32
|
+
|
|
29
33
|
@media (hover: hover) {
|
|
30
|
-
&:not([aria-disabled=
|
|
34
|
+
&:not([aria-disabled='true']):hover {
|
|
31
35
|
color: var(--webchat-colorNeutralForeground2BrandHover);
|
|
32
36
|
}
|
|
33
37
|
}
|
|
34
|
-
|
|
38
|
+
|
|
39
|
+
&:not([aria-disabled='true']):active {
|
|
35
40
|
color: var(--webchat-colorNeutralForeground2BrandPressed);
|
|
36
41
|
}
|
|
37
|
-
|
|
42
|
+
|
|
43
|
+
&[aria-disabled='true'] {
|
|
38
44
|
color: var(--webchat-colorNeutralForegroundDisabled);
|
|
39
45
|
cursor: not-allowed;
|
|
40
46
|
}
|
|
@@ -45,7 +51,9 @@
|
|
|
45
51
|
border-inline-end: 1px solid var(--webchat-colorNeutralStroke2);
|
|
46
52
|
height: 28px;
|
|
47
53
|
|
|
48
|
-
&:first-child,
|
|
49
|
-
|
|
54
|
+
&:first-child,
|
|
55
|
+
&:last-child,
|
|
56
|
+
&:only-child {
|
|
57
|
+
display: none;
|
|
50
58
|
}
|
|
51
59
|
}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import { hooks } from 'botframework-webchat-api';
|
|
1
2
|
import cx from 'classnames';
|
|
2
3
|
import React, { memo, type MouseEventHandler, type ReactNode } from 'react';
|
|
3
|
-
import styles from './Toolbar.module.css';
|
|
4
4
|
import { useStyles } from '../../styles';
|
|
5
|
+
import styles from './Toolbar.module.css';
|
|
6
|
+
|
|
7
|
+
const { useUIState } = hooks;
|
|
5
8
|
|
|
6
9
|
const preventDefaultHandler: MouseEventHandler<HTMLButtonElement> = event => event.preventDefault();
|
|
7
10
|
|
|
@@ -19,20 +22,22 @@ export const ToolbarButton = memo(
|
|
|
19
22
|
}>
|
|
20
23
|
) => {
|
|
21
24
|
const classNames = useStyles(styles);
|
|
25
|
+
const [uiState] = useUIState();
|
|
26
|
+
|
|
27
|
+
const disabled = props.disabled || uiState === 'disabled';
|
|
22
28
|
|
|
23
29
|
return (
|
|
24
30
|
<button
|
|
31
|
+
aria-disabled={disabled ? 'true' : undefined}
|
|
25
32
|
aria-label={props['aria-label']}
|
|
26
33
|
className={cx(classNames['sendbox__toolbar-button'], props.className, {
|
|
27
34
|
[classNames['sendbox__toolbar-button--selected']]: props.selected
|
|
28
35
|
})}
|
|
29
36
|
data-testid={props['data-testid']}
|
|
30
|
-
onClick={
|
|
37
|
+
onClick={disabled ? preventDefaultHandler : props.onClick}
|
|
38
|
+
// eslint-disable-next-line no-magic-numbers
|
|
39
|
+
tabIndex={disabled ? -1 : undefined}
|
|
31
40
|
type={props.type === 'submit' ? 'submit' : 'button'}
|
|
32
|
-
{...(props.disabled && {
|
|
33
|
-
'aria-disabled': 'true',
|
|
34
|
-
tabIndex: -1
|
|
35
|
-
})}
|
|
36
41
|
>
|
|
37
42
|
{props.children}
|
|
38
43
|
</button>
|
|
@@ -43,9 +48,14 @@ export const ToolbarButton = memo(
|
|
|
43
48
|
ToolbarButton.displayName = 'ToolbarButton';
|
|
44
49
|
|
|
45
50
|
export const Toolbar = memo((props: Readonly<{ children?: ReactNode | undefined; className?: string | undefined }>) => {
|
|
51
|
+
const [uiState] = useUIState();
|
|
46
52
|
const classNames = useStyles(styles);
|
|
47
53
|
|
|
48
|
-
return
|
|
54
|
+
return (
|
|
55
|
+
<div className={cx(classNames['sendbox__toolbar'], props.className)}>
|
|
56
|
+
{uiState !== 'blueprint' && props.children}
|
|
57
|
+
</div>
|
|
58
|
+
);
|
|
49
59
|
});
|
|
50
60
|
|
|
51
61
|
Toolbar.displayName = 'Toolbar';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { default as SendBox } from './SendBox';
|
|
1
|
+
export { PrimarySendBox, default as SendBox } from './SendBox';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { hooks } from 'botframework-webchat-component';
|
|
2
|
-
import {
|
|
2
|
+
import { useCallback, useMemo, useState } from 'react';
|
|
3
3
|
import { useRefFrom } from 'use-ref-from';
|
|
4
4
|
|
|
5
5
|
const { useConnectivityStatus, useLocalizer } = hooks;
|
|
@@ -16,6 +16,7 @@ const useSubmitError = ({
|
|
|
16
16
|
message: string;
|
|
17
17
|
}>) => {
|
|
18
18
|
const [connectivityStatus] = useConnectivityStatus();
|
|
19
|
+
const [error, setError] = useState<SendError | undefined>();
|
|
19
20
|
const localize = useLocalizer();
|
|
20
21
|
|
|
21
22
|
const submitErrorRef = useRefFrom<'empty' | 'offline' | undefined>(
|
|
@@ -37,9 +38,21 @@ const useSubmitError = ({
|
|
|
37
38
|
[localize]
|
|
38
39
|
);
|
|
39
40
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
// TODO: we may want to improve this later e.g. to avoid re-render
|
|
42
|
+
// Reset visible error if there is a value
|
|
43
|
+
const hasValue = !!message?.trim();
|
|
44
|
+
if (error === 'empty' && hasValue) {
|
|
45
|
+
setError(undefined);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const commitLatestError = useCallback(() => {
|
|
49
|
+
setError(submitErrorRef.current);
|
|
50
|
+
return submitErrorRef.current;
|
|
51
|
+
}, [submitErrorRef]);
|
|
52
|
+
|
|
53
|
+
return useMemo<Readonly<[string | undefined, () => typeof submitErrorRef.current]>>(
|
|
54
|
+
() => Object.freeze([error && errorMessageStringMap.get(error), commitLatestError]),
|
|
55
|
+
[error, errorMessageStringMap, commitLatestError]
|
|
43
56
|
);
|
|
44
57
|
};
|
|
45
58
|
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { useCallback, type KeyboardEvent } from 'react';
|
|
2
|
+
import { hooks } from 'botframework-webchat-component';
|
|
3
|
+
|
|
4
|
+
const { useScrollDown, useScrollUp } = hooks;
|
|
5
|
+
|
|
6
|
+
export default function useTranscriptNavigation() {
|
|
7
|
+
const scrollDown = useScrollDown();
|
|
8
|
+
const scrollUp = useScrollUp();
|
|
9
|
+
|
|
10
|
+
return useCallback(
|
|
11
|
+
(event: KeyboardEvent<unknown>) => {
|
|
12
|
+
if (event.target instanceof HTMLTextAreaElement && event.target.value) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const { ctrlKey, metaKey, shiftKey } = event;
|
|
17
|
+
|
|
18
|
+
if (ctrlKey || metaKey || shiftKey) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
let handled = true;
|
|
23
|
+
|
|
24
|
+
switch (event.key) {
|
|
25
|
+
case 'End':
|
|
26
|
+
scrollDown({ displacement: Infinity });
|
|
27
|
+
break;
|
|
28
|
+
|
|
29
|
+
case 'Home':
|
|
30
|
+
scrollUp({ displacement: Infinity });
|
|
31
|
+
break;
|
|
32
|
+
|
|
33
|
+
case 'PageDown':
|
|
34
|
+
scrollDown();
|
|
35
|
+
break;
|
|
36
|
+
|
|
37
|
+
case 'PageUp':
|
|
38
|
+
scrollUp();
|
|
39
|
+
break;
|
|
40
|
+
|
|
41
|
+
default:
|
|
42
|
+
handled = false;
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (handled) {
|
|
47
|
+
event.preventDefault();
|
|
48
|
+
event.stopPropagation();
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
[scrollDown, scrollUp]
|
|
52
|
+
);
|
|
53
|
+
}
|
|
@@ -3,12 +3,13 @@ import React, { MouseEventHandler, ReactNode, forwardRef, memo, useRef } from 'r
|
|
|
3
3
|
const preventDefaultHandler: MouseEventHandler<HTMLButtonElement> = event => event.preventDefault();
|
|
4
4
|
|
|
5
5
|
type AccessibleButtonProps = Readonly<{
|
|
6
|
+
'aria-hidden'?: boolean | undefined;
|
|
7
|
+
'data-testid'?: string | undefined;
|
|
8
|
+
children?: ReactNode | undefined;
|
|
6
9
|
className?: string | undefined;
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
onClick?: MouseEventHandler<HTMLButtonElement>;
|
|
11
|
-
tabIndex?: number;
|
|
10
|
+
disabled?: boolean | undefined;
|
|
11
|
+
onClick?: MouseEventHandler<HTMLButtonElement> | undefined;
|
|
12
|
+
tabIndex?: number | undefined;
|
|
12
13
|
type: 'button';
|
|
13
14
|
}>;
|
|
14
15
|
|
|
@@ -31,23 +32,24 @@ type AccessibleButtonProps = Readonly<{
|
|
|
31
32
|
// - If the widget is contained by a <form>, the developer need to filter out some `onSubmit` event caused by this widget
|
|
32
33
|
|
|
33
34
|
const AccessibleButton = forwardRef<HTMLButtonElement, AccessibleButtonProps>(
|
|
34
|
-
(
|
|
35
|
+
(
|
|
36
|
+
{ 'aria-hidden': ariaHidden, children, className, 'data-testid': dataTestId, disabled, onClick, tabIndex },
|
|
37
|
+
forwardedRef
|
|
38
|
+
) => {
|
|
35
39
|
const targetRef = useRef<HTMLButtonElement>(null);
|
|
36
40
|
|
|
37
41
|
const ref = forwardedRef || targetRef;
|
|
38
42
|
|
|
39
43
|
return (
|
|
40
44
|
<button
|
|
41
|
-
aria-disabled={disabled ? 'true' :
|
|
45
|
+
aria-disabled={disabled ? 'true' : undefined}
|
|
42
46
|
aria-hidden={ariaHidden}
|
|
47
|
+
className={className}
|
|
48
|
+
data-testid={dataTestId}
|
|
43
49
|
onClick={disabled ? preventDefaultHandler : onClick}
|
|
44
50
|
ref={ref}
|
|
45
|
-
|
|
46
|
-
{
|
|
47
|
-
'aria-disabled': 'true',
|
|
48
|
-
tabIndex: -1
|
|
49
|
-
})}
|
|
50
|
-
{...props}
|
|
51
|
+
// eslint-disable-next-line no-magic-numbers
|
|
52
|
+
tabIndex={disabled ? -1 : tabIndex}
|
|
51
53
|
type="button"
|
|
52
54
|
>
|
|
53
55
|
{children}
|
|
@@ -1,35 +1,36 @@
|
|
|
1
1
|
:global(.webchat-fluent) .suggested-action {
|
|
2
2
|
align-items: center;
|
|
3
3
|
background: transparent;
|
|
4
|
-
border-radius:
|
|
5
|
-
border:
|
|
4
|
+
border-radius: var(--webchat-borderRadiusXLarge);
|
|
5
|
+
border: var(--webchat-strokeWidthThin) solid var(--webchat-colorBrandStroke2);
|
|
6
6
|
color: currentColor;
|
|
7
7
|
cursor: pointer;
|
|
8
8
|
display: flex;
|
|
9
|
-
font-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
font-family: var(--webchat__font--primary);
|
|
10
|
+
font-size: var(--webchat-fontSizeBase200);
|
|
11
|
+
gap: var(--webchat-spacingHorizontalXS);
|
|
12
|
+
padding: var(--webchat-spacingVerticalXS) var(--webchat-spacingHorizontalS);
|
|
12
13
|
text-align: start;
|
|
13
|
-
transition: all
|
|
14
|
+
transition: all var(--webchat-durationNormal) var(--webchat-curveDecelerateMid);
|
|
14
15
|
|
|
15
16
|
@media (hover: hover) {
|
|
16
|
-
&:not([aria-disabled=
|
|
17
|
+
&:not([aria-disabled='true']):hover {
|
|
17
18
|
background-color: var(--webchat-colorBrandBackground2Hover);
|
|
18
|
-
color: var(--webchat-colorBrandForeground2Hover)
|
|
19
|
+
color: var(--webchat-colorBrandForeground2Hover);
|
|
19
20
|
}
|
|
20
21
|
}
|
|
21
|
-
&:not([aria-disabled=
|
|
22
|
+
&:not([aria-disabled='true']):active {
|
|
22
23
|
background-color: var(--webchat-colorBrandBackground2Pressed);
|
|
23
|
-
color: var(--webchat-colorBrandForeground2Pressed)
|
|
24
|
+
color: var(--webchat-colorBrandForeground2Pressed);
|
|
24
25
|
}
|
|
25
|
-
&[aria-disabled=
|
|
26
|
+
&[aria-disabled='true'] {
|
|
26
27
|
color: var(--webchat-colorNeutralForegroundDisabled);
|
|
27
|
-
cursor: not-allowed
|
|
28
|
+
cursor: not-allowed;
|
|
28
29
|
}
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
:global(.webchat-fluent) .suggested-action__image {
|
|
32
|
-
font-size:
|
|
33
|
+
font-size: var(--webchat-fontSizeBase200);
|
|
33
34
|
height: 1em;
|
|
34
35
|
width: 1em;
|
|
35
36
|
}
|
|
@@ -2,12 +2,14 @@ import { hooks } from 'botframework-webchat-component';
|
|
|
2
2
|
import { type DirectLineCardAction } from 'botframework-webchat-core';
|
|
3
3
|
import cx from 'classnames';
|
|
4
4
|
import React, { MouseEventHandler, memo, useCallback } from 'react';
|
|
5
|
-
|
|
5
|
+
|
|
6
6
|
import { useStyles } from '../../styles';
|
|
7
|
+
import testIds from '../../testIds';
|
|
7
8
|
import AccessibleButton from './AccessibleButton';
|
|
8
9
|
import { useRovingFocusItemRef } from './private/rovingFocus';
|
|
10
|
+
import styles from './SuggestedAction.module.css';
|
|
9
11
|
|
|
10
|
-
const {
|
|
12
|
+
const { useFocus, usePerformCardAction, useScrollToEnd, useStyleSet, useSuggestedActions, useUIState } = hooks;
|
|
11
13
|
|
|
12
14
|
type SuggestedActionProps = Readonly<{
|
|
13
15
|
buttonText: string | undefined;
|
|
@@ -44,7 +46,7 @@ function SuggestedAction({
|
|
|
44
46
|
}: SuggestedActionProps) {
|
|
45
47
|
const [_, setSuggestedActions] = useSuggestedActions();
|
|
46
48
|
const [{ suggestedAction: suggestedActionStyleSet }] = useStyleSet();
|
|
47
|
-
const [
|
|
49
|
+
const [uiState] = useUIState();
|
|
48
50
|
const focus = useFocus();
|
|
49
51
|
const focusRef = useRovingFocusItemRef<HTMLButtonElement>(itemIndex);
|
|
50
52
|
const performCardAction = usePerformCardAction();
|
|
@@ -75,7 +77,8 @@ function SuggestedAction({
|
|
|
75
77
|
return (
|
|
76
78
|
<AccessibleButton
|
|
77
79
|
className={cx(classNames['suggested-action'], suggestedActionStyleSet + '', (className || '') + '')}
|
|
78
|
-
|
|
80
|
+
data-testid={testIds.sendBoxSuggestedAction}
|
|
81
|
+
disabled={uiState === 'disabled'}
|
|
79
82
|
onClick={handleClick}
|
|
80
83
|
ref={focusRef}
|
|
81
84
|
type="button"
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
|
|
2
1
|
:global(.webchat-fluent) .suggested-actions {
|
|
3
2
|
align-items: flex-end;
|
|
4
3
|
align-self: flex-end;
|
|
@@ -8,7 +7,7 @@
|
|
|
8
7
|
|
|
9
8
|
&:not(:empty) {
|
|
10
9
|
padding-block-end: 8px;
|
|
11
|
-
padding-inline-start: 4px
|
|
10
|
+
padding-inline-start: 4px;
|
|
12
11
|
}
|
|
13
12
|
|
|
14
13
|
&.suggested-actions--flow {
|
|
@@ -18,6 +17,6 @@
|
|
|
18
17
|
}
|
|
19
18
|
|
|
20
19
|
&.suggested-actions--stacked {
|
|
21
|
-
flex-direction: column
|
|
20
|
+
flex-direction: column;
|
|
22
21
|
}
|
|
23
22
|
}
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { hooks } from 'botframework-webchat-component';
|
|
2
2
|
import cx from 'classnames';
|
|
3
3
|
import React, { memo, useCallback, type ReactNode } from 'react';
|
|
4
|
-
import SuggestedAction from './SuggestedAction';
|
|
5
|
-
import computeSuggestedActionText from './private/computeSuggestedActionText';
|
|
6
|
-
import styles from './SuggestedActions.module.css';
|
|
7
4
|
import { useStyles } from '../../styles';
|
|
5
|
+
import { isPreChatMessageActivity } from '../preChatActivity';
|
|
6
|
+
import computeSuggestedActionText from './private/computeSuggestedActionText';
|
|
8
7
|
import RovingFocusProvider from './private/rovingFocus';
|
|
8
|
+
import SuggestedAction from './SuggestedAction';
|
|
9
|
+
import styles from './SuggestedActions.module.css';
|
|
9
10
|
|
|
10
|
-
const { useFocus, useLocalizer, useStyleOptions, useStyleSet, useSuggestedActions } = hooks;
|
|
11
|
+
const { useFocus, useLocalizer, useStyleOptions, useStyleSet, useSuggestedActions, useUIState } = hooks;
|
|
11
12
|
|
|
12
13
|
function SuggestedActionStackedOrFlowContainer(
|
|
13
14
|
props: Readonly<{
|
|
@@ -18,12 +19,12 @@ function SuggestedActionStackedOrFlowContainer(
|
|
|
18
19
|
) {
|
|
19
20
|
const [{ suggestedActionLayout }] = useStyleOptions();
|
|
20
21
|
const [{ suggestedActions: suggestedActionsStyleSet }] = useStyleSet();
|
|
22
|
+
const [uiState] = useUIState();
|
|
21
23
|
const classNames = useStyles(styles);
|
|
22
24
|
|
|
23
25
|
return (
|
|
24
26
|
<div
|
|
25
27
|
aria-label={props['aria-label']}
|
|
26
|
-
aria-live="polite"
|
|
27
28
|
aria-orientation="vertical"
|
|
28
29
|
className={cx(
|
|
29
30
|
classNames['suggested-actions'],
|
|
@@ -36,7 +37,7 @@ function SuggestedActionStackedOrFlowContainer(
|
|
|
36
37
|
)}
|
|
37
38
|
role="toolbar"
|
|
38
39
|
>
|
|
39
|
-
{
|
|
40
|
+
{uiState !== 'blueprint' && props.children}
|
|
40
41
|
</div>
|
|
41
42
|
);
|
|
42
43
|
}
|
|
@@ -44,54 +45,56 @@ function SuggestedActionStackedOrFlowContainer(
|
|
|
44
45
|
function SuggestedActions() {
|
|
45
46
|
const classNames = useStyles(styles);
|
|
46
47
|
const localize = useLocalizer();
|
|
47
|
-
const [suggestedActions] = useSuggestedActions();
|
|
48
|
+
const [suggestedActions, _, { activity }] = useSuggestedActions();
|
|
48
49
|
const focus = useFocus();
|
|
49
50
|
|
|
50
51
|
const handleEscapeKey = useCallback(() => {
|
|
51
52
|
focus('sendBox');
|
|
52
53
|
}, [focus]);
|
|
53
54
|
|
|
54
|
-
const children =
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
55
|
+
const children = isPreChatMessageActivity(activity)
|
|
56
|
+
? [] // Do not show suggested actions for pre-chat message, suggested actions has already shown inlined.
|
|
57
|
+
: suggestedActions.map((cardAction, index) => {
|
|
58
|
+
const { displayText, image, imageAltText, text, type, value } = cardAction as {
|
|
59
|
+
displayText?: string;
|
|
60
|
+
image?: string;
|
|
61
|
+
imageAltText?: string;
|
|
62
|
+
text?: string;
|
|
63
|
+
type:
|
|
64
|
+
| 'call'
|
|
65
|
+
| 'downloadFile'
|
|
66
|
+
| 'imBack'
|
|
67
|
+
| 'messageBack'
|
|
68
|
+
| 'openUrl'
|
|
69
|
+
| 'playAudio'
|
|
70
|
+
| 'playVideo'
|
|
71
|
+
| 'postBack'
|
|
72
|
+
| 'showImage'
|
|
73
|
+
| 'signin';
|
|
74
|
+
value?: { [key: string]: any } | string;
|
|
75
|
+
};
|
|
73
76
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
+
if (!suggestedActions?.length) {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
77
80
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
81
|
+
return (
|
|
82
|
+
<SuggestedAction
|
|
83
|
+
buttonText={computeSuggestedActionText(cardAction)}
|
|
84
|
+
displayText={displayText}
|
|
85
|
+
image={image}
|
|
86
|
+
// Image alt text should use `imageAltText` field and fallback to `text` field.
|
|
87
|
+
// https://github.com/microsoft/botframework-sdk/blob/main/specs/botframework-activity/botframework-activity.md#image-alt-text
|
|
88
|
+
imageAlt={image && (imageAltText || text)}
|
|
89
|
+
itemIndex={index}
|
|
90
|
+
// eslint-disable-next-line react/no-array-index-key
|
|
91
|
+
key={index}
|
|
92
|
+
text={text}
|
|
93
|
+
type={type}
|
|
94
|
+
value={value}
|
|
95
|
+
/>
|
|
96
|
+
);
|
|
97
|
+
});
|
|
95
98
|
|
|
96
99
|
return (
|
|
97
100
|
<RovingFocusProvider onEscapeKey={handleEscapeKey}>
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
|
|
2
1
|
:global(.webchat-fluent) .telephone-keypad__button {
|
|
3
2
|
-webkit-user-select: none;
|
|
4
3
|
align-items: center;
|
|
@@ -28,7 +27,7 @@
|
|
|
28
27
|
|
|
29
28
|
&:hover {
|
|
30
29
|
/* backgroundColor: isHighContrastTheme() ? gray210 : isDarkTheme() ? gray150 : gray30 */
|
|
31
|
-
background-color: var(--webchat-colorGray30)
|
|
30
|
+
background-color: var(--webchat-colorGray30);
|
|
32
31
|
}
|
|
33
32
|
}
|
|
34
33
|
|
|
@@ -49,7 +48,7 @@
|
|
|
49
48
|
justify-content: center;
|
|
50
49
|
margin: 8px 4px;
|
|
51
50
|
width: 32px;
|
|
52
|
-
}
|
|
51
|
+
}
|
|
53
52
|
|
|
54
53
|
.telephone-keypad__button__ruby {
|
|
55
54
|
display: none;
|