botframework-webchat-fluent-theme 4.18.1-main.20240830.4534802 → 4.18.1-main.20240911.3e47786
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 +2 -0
- package/dist/botframework-webchat-fluent-theme.d.ts +2 -0
- package/dist/botframework-webchat-fluent-theme.development.css.map +1 -1
- package/dist/botframework-webchat-fluent-theme.development.js +8 -8
- 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 +8 -8
- package/dist/botframework-webchat-fluent-theme.production.min.js.map +1 -1
- package/package.json +4 -4
- package/src/components/activity/ActivityDecorator.module.css +35 -17
- package/src/components/activity/ActivityDecorator.tsx +1 -1
- package/src/components/activity/CopilotMessageHeader.module.css +5 -3
- package/src/components/activity/CopilotMessageHeader.tsx +20 -12
- package/src/components/activity/index.ts +1 -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/decorator/private/BorderFlair.module.css +4 -0
- package/src/components/decorator/private/BorderFlair.tsx +15 -2
- 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 +21 -7
- package/src/components/preChatActivity/PreChatMessageActivity.tsx +32 -26
- package/src/components/preChatActivity/StarterPromptsCardAction.module.css +55 -16
- package/src/components/preChatActivity/StarterPromptsCardAction.tsx +30 -24
- package/src/components/preChatActivity/StarterPromptsToolbar.tsx +23 -10
- package/src/components/sendBox/Attachments.tsx +5 -4
- package/src/components/sendBox/SendBox.module.css +7 -0
- package/src/components/sendBox/SendBox.tsx +31 -17
- package/src/components/sendBox/TextArea.tsx +50 -30
- package/src/components/sendBox/Toolbar.module.css +7 -1
- package/src/components/sendBox/Toolbar.tsx +17 -7
- package/src/components/suggestedActions/AccessibleButton.tsx +15 -13
- package/src/components/suggestedActions/SuggestedAction.module.css +8 -7
- package/src/components/suggestedActions/SuggestedAction.tsx +7 -4
- package/src/components/suggestedActions/SuggestedActions.tsx +3 -2
- package/src/components/theme/Theme.module.css +25 -8
- package/src/private/FluentThemeProvider.tsx +7 -1
- package/src/testIds.ts +2 -0
|
@@ -7,7 +7,7 @@ import { useStyles } from '../../styles/index.js';
|
|
|
7
7
|
import testIds from '../../testIds.js';
|
|
8
8
|
import styles from './StarterPromptsCardAction.module.css';
|
|
9
9
|
|
|
10
|
-
const { useFocus, useRenderMarkdownAsHTML, useSendBoxValue } = hooks;
|
|
10
|
+
const { useFocus, useRenderMarkdownAsHTML, useSendBoxValue, useUIState } = hooks;
|
|
11
11
|
const { MonochromeImageMasker } = Components;
|
|
12
12
|
|
|
13
13
|
type Props = Readonly<{
|
|
@@ -17,14 +17,20 @@ type Props = Readonly<{
|
|
|
17
17
|
|
|
18
18
|
const StarterPromptsCardAction = ({ className, messageBackAction }: Props) => {
|
|
19
19
|
const [_, setSendBoxValue] = useSendBoxValue();
|
|
20
|
+
const [uiState] = useUIState();
|
|
20
21
|
const classNames = useStyles(styles);
|
|
21
22
|
const focus = useFocus();
|
|
22
23
|
const inputTextRef = useRefFrom(messageBackAction?.displayText || messageBackAction?.text || '');
|
|
23
24
|
const renderMarkdownAsHTML = useRenderMarkdownAsHTML('message activity');
|
|
24
|
-
const subtitleHTML = useMemo(
|
|
25
|
-
() => (renderMarkdownAsHTML ? { __html: renderMarkdownAsHTML(messageBackAction?.text || '') } :
|
|
25
|
+
const subtitleHTML = useMemo<{ __html: string } | undefined>(
|
|
26
|
+
() => (renderMarkdownAsHTML ? { __html: renderMarkdownAsHTML(messageBackAction?.text || '') } : undefined),
|
|
26
27
|
[messageBackAction?.text, renderMarkdownAsHTML]
|
|
27
28
|
);
|
|
29
|
+
const disabled = uiState === 'disabled';
|
|
30
|
+
const title = messageBackAction && 'title' in messageBackAction && messageBackAction.title;
|
|
31
|
+
|
|
32
|
+
// Every starter prompt card action must have "title" field.
|
|
33
|
+
const shouldShowBlueprint = uiState === 'blueprint' || !title;
|
|
28
34
|
|
|
29
35
|
const handleClick = useCallback(() => {
|
|
30
36
|
setSendBoxValue(inputTextRef.current);
|
|
@@ -33,33 +39,33 @@ const StarterPromptsCardAction = ({ className, messageBackAction }: Props) => {
|
|
|
33
39
|
focus('sendBox');
|
|
34
40
|
}, [focus, inputTextRef, setSendBoxValue]);
|
|
35
41
|
|
|
36
|
-
return (
|
|
42
|
+
return shouldShowBlueprint ? (
|
|
43
|
+
<div
|
|
44
|
+
className={cx(className, classNames['pre-chat-message-activity__card-action-box'])}
|
|
45
|
+
data-testid={testIds.preChatMessageActivityStarterPromptsCardAction}
|
|
46
|
+
/>
|
|
47
|
+
) : (
|
|
37
48
|
<button
|
|
49
|
+
aria-disabled={disabled ? true : undefined}
|
|
38
50
|
className={cx(className, classNames['pre-chat-message-activity__card-action-box'])}
|
|
39
51
|
data-testid={testIds.preChatMessageActivityStarterPromptsCardAction}
|
|
40
|
-
|
|
41
|
-
|
|
52
|
+
onClick={disabled ? undefined : handleClick}
|
|
53
|
+
// eslint-disable-next-line no-magic-numbers
|
|
54
|
+
tabIndex={disabled ? -1 : undefined}
|
|
42
55
|
type="button"
|
|
43
56
|
>
|
|
44
|
-
{
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
<MonochromeImageMasker
|
|
51
|
-
className={classNames['pre-chat-message-activity__card-action-image']}
|
|
52
|
-
src={messageBackAction.image}
|
|
53
|
-
/>
|
|
54
|
-
// <img className="pre-chat-message-activity__card-action-image" src={messageBackAction.image} />
|
|
55
|
-
)}
|
|
56
|
-
<div
|
|
57
|
-
className={classNames['pre-chat-message-activity__card-action-subtitle']}
|
|
58
|
-
// eslint-disable-next-line react/no-danger
|
|
59
|
-
dangerouslySetInnerHTML={subtitleHTML}
|
|
60
|
-
/>
|
|
61
|
-
</React.Fragment>
|
|
57
|
+
<div className={classNames['pre-chat-message-activity__card-action-title']}>{title}</div>
|
|
58
|
+
{'image' in messageBackAction && messageBackAction.image && (
|
|
59
|
+
<MonochromeImageMasker
|
|
60
|
+
className={classNames['pre-chat-message-activity__card-action-image']}
|
|
61
|
+
src={messageBackAction.image}
|
|
62
|
+
/>
|
|
62
63
|
)}
|
|
64
|
+
<div
|
|
65
|
+
className={classNames['pre-chat-message-activity__card-action-subtitle']}
|
|
66
|
+
// eslint-disable-next-line react/no-danger
|
|
67
|
+
dangerouslySetInnerHTML={subtitleHTML}
|
|
68
|
+
/>
|
|
63
69
|
</button>
|
|
64
70
|
);
|
|
65
71
|
};
|
|
@@ -1,34 +1,47 @@
|
|
|
1
|
+
import { hooks } from 'botframework-webchat-api';
|
|
1
2
|
import { type DirectLineCardAction } from 'botframework-webchat-core';
|
|
2
3
|
import cx from 'classnames';
|
|
3
|
-
import React, {
|
|
4
|
+
import React, { Fragment, memo } from 'react';
|
|
4
5
|
import { useStyles } from '../../styles/index.js';
|
|
5
6
|
import StarterPromptsCardAction from './StarterPromptsCardAction.js';
|
|
6
7
|
import styles from './StarterPromptsToolbar.module.css';
|
|
7
8
|
|
|
9
|
+
const { useUIState } = hooks;
|
|
10
|
+
|
|
8
11
|
type Props = Readonly<{
|
|
9
12
|
cardActions: readonly DirectLineCardAction[];
|
|
10
13
|
className?: string | undefined;
|
|
11
|
-
children?: ReactNode | undefined;
|
|
12
14
|
}>;
|
|
13
15
|
|
|
14
|
-
const
|
|
16
|
+
const StarterPromptsToolbar = ({ cardActions, className }: Props) => {
|
|
15
17
|
const classNames = useStyles(styles);
|
|
18
|
+
const [uiState] = useUIState();
|
|
16
19
|
|
|
17
20
|
return (
|
|
18
21
|
// TODO: Accessibility-wise, this should be role="toolbar" with keyboard navigation.
|
|
19
22
|
<div className={cx(className, classNames['pre-chat-message-activity__card-action-toolbar'])}>
|
|
20
23
|
<div className={classNames['pre-chat-message-activity__card-action-toolbar-grid']}>
|
|
21
|
-
{
|
|
24
|
+
{uiState === 'blueprint' ? (
|
|
25
|
+
<Fragment>
|
|
26
|
+
<StarterPromptsCardAction />
|
|
27
|
+
<StarterPromptsCardAction />
|
|
28
|
+
<StarterPromptsCardAction />
|
|
29
|
+
</Fragment>
|
|
30
|
+
) : (
|
|
22
31
|
cardActions
|
|
23
|
-
.filter<
|
|
24
|
-
DirectLineCardAction & { type: 'messageBack' }
|
|
25
|
-
|
|
26
|
-
|
|
32
|
+
.filter<DirectLineCardAction & { type: 'messageBack' }>(
|
|
33
|
+
(card: DirectLineCardAction): card is DirectLineCardAction & { type: 'messageBack' } =>
|
|
34
|
+
card.type === 'messageBack'
|
|
35
|
+
)
|
|
36
|
+
// There is no other usable keys in card actions.
|
|
37
|
+
// eslint-disable-next-line react/no-array-index-key
|
|
38
|
+
.map((cardAction, index) => <StarterPromptsCardAction key={index} messageBackAction={cardAction} />)
|
|
39
|
+
)}
|
|
27
40
|
</div>
|
|
28
41
|
</div>
|
|
29
42
|
);
|
|
30
43
|
};
|
|
31
44
|
|
|
32
|
-
|
|
45
|
+
StarterPromptsToolbar.displayName = 'StarterPromptsToolbar';
|
|
33
46
|
|
|
34
|
-
export default memo(
|
|
47
|
+
export default memo(StarterPromptsToolbar);
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { hooks } from 'botframework-webchat-component';
|
|
2
|
-
import React, { memo } from 'react';
|
|
3
2
|
import cx from 'classnames';
|
|
4
|
-
import
|
|
3
|
+
import React, { memo } from 'react';
|
|
5
4
|
import { useStyles } from '../../styles';
|
|
5
|
+
import styles from './Attachments.module.css';
|
|
6
6
|
|
|
7
|
-
const { useLocalizer } = hooks;
|
|
7
|
+
const { useLocalizer, useUIState } = hooks;
|
|
8
8
|
|
|
9
9
|
const attachmentsPluralStringIds = {
|
|
10
10
|
one: 'TEXT_INPUT_ATTACHMENTS_ONE',
|
|
@@ -21,10 +21,11 @@ function Attachments({
|
|
|
21
21
|
readonly attachments: readonly Readonly<{ blob: Blob | File; thumbnailURL?: URL | undefined }>[];
|
|
22
22
|
readonly className?: string | undefined;
|
|
23
23
|
}>) {
|
|
24
|
+
const [uiState] = useUIState();
|
|
24
25
|
const classNames = useStyles(styles);
|
|
25
26
|
const localizeWithPlural = useLocalizer({ plural: true });
|
|
26
27
|
|
|
27
|
-
return attachments.length ? (
|
|
28
|
+
return uiState !== 'blueprint' && attachments.length ? (
|
|
28
29
|
<div className={cx(classNames['sendbox__attachment'], className)}>
|
|
29
30
|
{localizeWithPlural(attachmentsPluralStringIds, attachments.length)}
|
|
30
31
|
</div>
|
|
@@ -10,6 +10,13 @@
|
|
|
10
10
|
--webchat-sendbox-border-radius: var(--webchat-borderRadiusLarge);
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
/* Copilot variant */
|
|
14
|
+
:global(.webchat-fluent) .sendbox.variant-copilot {
|
|
15
|
+
.sendbox__text-counter:not(.sendbox__text-counter--error) {
|
|
16
|
+
visibility: hidden;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
13
20
|
:global(.webchat-fluent) .sendbox__sendbox {
|
|
14
21
|
background-color: var(--webchat-colorNeutralBackground1);
|
|
15
22
|
border-radius: var(--webchat-sendbox-border-radius);
|
|
@@ -3,7 +3,7 @@ import cx from 'classnames';
|
|
|
3
3
|
import React, { memo, useCallback, useRef, useState, type FormEventHandler, type MouseEventHandler } from 'react';
|
|
4
4
|
import { useRefFrom } from 'use-ref-from';
|
|
5
5
|
import { SendIcon } from '../../icons';
|
|
6
|
-
import { useStyles } from '../../styles';
|
|
6
|
+
import { useStyles, useVariantClassName } from '../../styles';
|
|
7
7
|
import testIds from '../../testIds';
|
|
8
8
|
import { DropZone } from '../dropZone';
|
|
9
9
|
import { SuggestedActions } from '../suggestedActions';
|
|
@@ -27,7 +27,8 @@ const {
|
|
|
27
27
|
useSendBoxAttachments,
|
|
28
28
|
useSendBoxValue,
|
|
29
29
|
useSendMessage,
|
|
30
|
-
useStyleOptions
|
|
30
|
+
useStyleOptions,
|
|
31
|
+
useUIState
|
|
31
32
|
} = hooks;
|
|
32
33
|
|
|
33
34
|
type Props = Readonly<{
|
|
@@ -37,23 +38,31 @@ type Props = Readonly<{
|
|
|
37
38
|
}>;
|
|
38
39
|
|
|
39
40
|
function SendBox(props: Props) {
|
|
40
|
-
const inputRef = useRef<HTMLTextAreaElement>(null);
|
|
41
|
-
const [localMessage, setLocalMessage] = useState('');
|
|
42
|
-
const [globalMessage, setGlobalMessage] = useSendBoxValue();
|
|
43
|
-
const message = props.isPrimary ? globalMessage : localMessage;
|
|
44
|
-
const setMessage = props.isPrimary ? setGlobalMessage : setLocalMessage;
|
|
45
|
-
const [attachments, setAttachments] = useSendBoxAttachments();
|
|
46
41
|
const [{ hideTelephoneKeypadButton, hideUploadButton, maxMessageLength }] = useStyleOptions();
|
|
47
|
-
const
|
|
42
|
+
const [attachments, setAttachments] = useSendBoxAttachments();
|
|
43
|
+
const [globalMessage, setGlobalMessage] = useSendBoxValue();
|
|
44
|
+
const [localMessage, setLocalMessage] = useState('');
|
|
45
|
+
const [telephoneKeypadShown] = useTelephoneKeypadShown();
|
|
46
|
+
const [uiState] = useUIState();
|
|
48
47
|
const classNames = useStyles(styles);
|
|
48
|
+
const variantClassName = useVariantClassName(styles);
|
|
49
|
+
const errorMessageId = useUniqueId('sendbox__error-message-id');
|
|
50
|
+
const inputRef = useRef<HTMLTextAreaElement>(null);
|
|
49
51
|
const localize = useLocalizer();
|
|
50
|
-
const sendMessage = useSendMessage();
|
|
51
52
|
const makeThumbnail = useMakeThumbnail();
|
|
52
|
-
const
|
|
53
|
-
const [errorMessage, commitLatestError] = useSubmitError({ message, attachments });
|
|
54
|
-
const [telephoneKeypadShown] = useTelephoneKeypadShown();
|
|
53
|
+
const sendMessage = useSendMessage();
|
|
55
54
|
const setFocus = useFocus();
|
|
56
55
|
|
|
56
|
+
const message = props.isPrimary ? globalMessage : localMessage;
|
|
57
|
+
const setMessage = props.isPrimary ? setGlobalMessage : setLocalMessage;
|
|
58
|
+
const isBlueprint = uiState === 'blueprint';
|
|
59
|
+
|
|
60
|
+
const [errorMessage, commitLatestError] = useSubmitError({ message, attachments });
|
|
61
|
+
const isMessageLengthExceeded = !!maxMessageLength && message.length > maxMessageLength;
|
|
62
|
+
const shouldShowMessageLength =
|
|
63
|
+
!isBlueprint && !telephoneKeypadShown && maxMessageLength && isFinite(maxMessageLength);
|
|
64
|
+
const shouldShowTelephoneKeypad = !isBlueprint && telephoneKeypadShown;
|
|
65
|
+
|
|
57
66
|
useRegisterFocusSendBox(
|
|
58
67
|
useCallback(
|
|
59
68
|
({ noKeyboard, waitUntil }: SendBoxFocusOptions) => {
|
|
@@ -174,7 +183,12 @@ function SendBox(props: Props) {
|
|
|
174
183
|
};
|
|
175
184
|
|
|
176
185
|
return (
|
|
177
|
-
<form
|
|
186
|
+
<form
|
|
187
|
+
{...aria}
|
|
188
|
+
className={cx(classNames['sendbox'], variantClassName, props.className)}
|
|
189
|
+
data-testid={testIds.sendBoxContainer}
|
|
190
|
+
onSubmit={handleFormSubmit}
|
|
191
|
+
>
|
|
178
192
|
<SuggestedActions />
|
|
179
193
|
<div
|
|
180
194
|
className={cx(classNames['sendbox__sendbox'])}
|
|
@@ -185,7 +199,7 @@ function SendBox(props: Props) {
|
|
|
185
199
|
aria-label={isMessageLengthExceeded ? localize('TEXT_INPUT_LENGTH_EXCEEDED_ALT') : localize('TEXT_INPUT_ALT')}
|
|
186
200
|
className={cx(classNames['sendbox__sendbox-text'], classNames['sendbox__text-area--in-grid'])}
|
|
187
201
|
data-testid={testIds.sendBoxTextBox}
|
|
188
|
-
hidden={
|
|
202
|
+
hidden={shouldShowTelephoneKeypad}
|
|
189
203
|
onInput={handleMessageChange}
|
|
190
204
|
placeholder={props.placeholder ?? localize('TEXT_INPUT_PLACEHOLDER')}
|
|
191
205
|
ref={inputRef}
|
|
@@ -199,7 +213,7 @@ function SendBox(props: Props) {
|
|
|
199
213
|
/>
|
|
200
214
|
<Attachments attachments={attachments} className={classNames['sendbox__attachment--in-grid']} />
|
|
201
215
|
<div className={cx(classNames['sendbox__sendbox-controls'], classNames['sendbox__sendbox-controls--in-grid'])}>
|
|
202
|
-
{
|
|
216
|
+
{shouldShowMessageLength && (
|
|
203
217
|
<div
|
|
204
218
|
className={cx(classNames['sendbox__text-counter'], {
|
|
205
219
|
[classNames['sendbox__text-counter--error']]: isMessageLengthExceeded
|
|
@@ -215,7 +229,7 @@ function SendBox(props: Props) {
|
|
|
215
229
|
<ToolbarButton
|
|
216
230
|
aria-label={localize('TEXT_INPUT_SEND_BUTTON_ALT')}
|
|
217
231
|
data-testid={testIds.sendBoxSendButton}
|
|
218
|
-
disabled={isMessageLengthExceeded ||
|
|
232
|
+
disabled={isMessageLengthExceeded || shouldShowTelephoneKeypad}
|
|
219
233
|
type="submit"
|
|
220
234
|
>
|
|
221
235
|
<SendIcon />
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
import { hooks } from 'botframework-webchat-api';
|
|
1
2
|
import cx from 'classnames';
|
|
2
|
-
import React, { forwardRef, useCallback, type FormEventHandler, type KeyboardEventHandler } from 'react';
|
|
3
|
+
import React, { forwardRef, Fragment, useCallback, type FormEventHandler, type KeyboardEventHandler } from 'react';
|
|
3
4
|
import { useStyles } from '../../styles';
|
|
4
5
|
import styles from './TextArea.module.css';
|
|
5
6
|
|
|
7
|
+
const { useUIState } = hooks;
|
|
8
|
+
|
|
6
9
|
const TextArea = forwardRef<
|
|
7
10
|
HTMLTextAreaElement,
|
|
8
11
|
Readonly<{
|
|
@@ -25,8 +28,11 @@ const TextArea = forwardRef<
|
|
|
25
28
|
value?: string | undefined;
|
|
26
29
|
}>
|
|
27
30
|
>((props, ref) => {
|
|
31
|
+
const [uiState] = useUIState();
|
|
28
32
|
const classNames = useStyles(styles);
|
|
29
33
|
|
|
34
|
+
const disabled = uiState === 'disabled';
|
|
35
|
+
|
|
30
36
|
const handleKeyDown = useCallback<KeyboardEventHandler<HTMLTextAreaElement>>(event => {
|
|
31
37
|
// Shift+Enter adds a new line
|
|
32
38
|
// Enter requests related form submission
|
|
@@ -43,39 +49,53 @@ const TextArea = forwardRef<
|
|
|
43
49
|
<div
|
|
44
50
|
className={cx(
|
|
45
51
|
classNames['sendbox__text-area'],
|
|
46
|
-
{
|
|
47
|
-
[classNames['sendbox__text-area--hidden']]: props.hidden
|
|
48
|
-
},
|
|
52
|
+
{ [classNames['sendbox__text-area--hidden']]: props.hidden },
|
|
49
53
|
props.className
|
|
50
54
|
)}
|
|
51
55
|
role={props.hidden ? 'hidden' : undefined}
|
|
52
56
|
>
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
57
|
+
{uiState === 'blueprint' ? (
|
|
58
|
+
<div
|
|
59
|
+
className={cx(
|
|
60
|
+
classNames['sendbox__text-area-doppelganger'],
|
|
61
|
+
classNames['sendbox__text-area-input--scroll'],
|
|
62
|
+
classNames['sendbox__text-area-shared']
|
|
63
|
+
)}
|
|
64
|
+
>
|
|
65
|
+
{' '}
|
|
66
|
+
</div>
|
|
67
|
+
) : (
|
|
68
|
+
<Fragment>
|
|
69
|
+
<div
|
|
70
|
+
className={cx(
|
|
71
|
+
classNames['sendbox__text-area-doppelganger'],
|
|
72
|
+
classNames['sendbox__text-area-input--scroll'],
|
|
73
|
+
classNames['sendbox__text-area-shared']
|
|
74
|
+
)}
|
|
75
|
+
>
|
|
76
|
+
{props.value || props.placeholder}{' '}
|
|
77
|
+
</div>
|
|
78
|
+
<textarea
|
|
79
|
+
aria-disabled={disabled}
|
|
80
|
+
aria-label={props['aria-label']}
|
|
81
|
+
className={cx(
|
|
82
|
+
classNames['sendbox__text-area-input'],
|
|
83
|
+
classNames['sendbox__text-area-input--scroll'],
|
|
84
|
+
classNames['sendbox__text-area-shared']
|
|
85
|
+
)}
|
|
86
|
+
data-testid={props['data-testid']}
|
|
87
|
+
onInput={props.onInput}
|
|
88
|
+
onKeyDown={handleKeyDown}
|
|
89
|
+
placeholder={props.placeholder}
|
|
90
|
+
readOnly={disabled}
|
|
91
|
+
ref={ref}
|
|
92
|
+
rows={props.startRows ?? 1}
|
|
93
|
+
// eslint-disable-next-line no-magic-numbers
|
|
94
|
+
tabIndex={props.hidden ? -1 : undefined}
|
|
95
|
+
value={props.value}
|
|
96
|
+
/>
|
|
97
|
+
</Fragment>
|
|
98
|
+
)}
|
|
79
99
|
</div>
|
|
80
100
|
);
|
|
81
101
|
});
|
|
@@ -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,9 +17,9 @@
|
|
|
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
|
> svg {
|
|
22
25
|
font-size: 20px;
|
|
@@ -26,14 +29,17 @@
|
|
|
26
29
|
&.sendbox__toolbar-button--selected {
|
|
27
30
|
color: var(--webchat-colorNeutralForeground2BrandSelected);
|
|
28
31
|
}
|
|
32
|
+
|
|
29
33
|
@media (hover: hover) {
|
|
30
34
|
&:not([aria-disabled="true"]):hover {
|
|
31
35
|
color: var(--webchat-colorNeutralForeground2BrandHover);
|
|
32
36
|
}
|
|
33
37
|
}
|
|
38
|
+
|
|
34
39
|
&:not([aria-disabled="true"]):active {
|
|
35
40
|
color: var(--webchat-colorNeutralForeground2BrandPressed);
|
|
36
41
|
}
|
|
42
|
+
|
|
37
43
|
&[aria-disabled="true"] {
|
|
38
44
|
color: var(--webchat-colorNeutralForegroundDisabled);
|
|
39
45
|
cursor: not-allowed;
|
|
@@ -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';
|
|
@@ -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,16 +1,17 @@
|
|
|
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
17
|
&:not([aria-disabled="true"]):hover {
|
|
@@ -29,7 +30,7 @@
|
|
|
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"
|
|
@@ -8,7 +8,7 @@ import RovingFocusProvider from './private/rovingFocus';
|
|
|
8
8
|
import SuggestedAction from './SuggestedAction';
|
|
9
9
|
import styles from './SuggestedActions.module.css';
|
|
10
10
|
|
|
11
|
-
const { useFocus, useLocalizer, useStyleOptions, useStyleSet, useSuggestedActions } = hooks;
|
|
11
|
+
const { useFocus, useLocalizer, useStyleOptions, useStyleSet, useSuggestedActions, useUIState } = hooks;
|
|
12
12
|
|
|
13
13
|
function SuggestedActionStackedOrFlowContainer(
|
|
14
14
|
props: Readonly<{
|
|
@@ -19,6 +19,7 @@ function SuggestedActionStackedOrFlowContainer(
|
|
|
19
19
|
) {
|
|
20
20
|
const [{ suggestedActionLayout }] = useStyleOptions();
|
|
21
21
|
const [{ suggestedActions: suggestedActionsStyleSet }] = useStyleSet();
|
|
22
|
+
const [uiState] = useUIState();
|
|
22
23
|
const classNames = useStyles(styles);
|
|
23
24
|
|
|
24
25
|
return (
|
|
@@ -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
|
}
|