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.
Files changed (46) hide show
  1. package/dist/botframework-webchat-fluent-theme.css.map +1 -1
  2. package/dist/botframework-webchat-fluent-theme.d.mts +2 -0
  3. package/dist/botframework-webchat-fluent-theme.d.ts +2 -0
  4. package/dist/botframework-webchat-fluent-theme.development.css.map +1 -1
  5. package/dist/botframework-webchat-fluent-theme.development.js +8 -8
  6. package/dist/botframework-webchat-fluent-theme.development.js.map +1 -1
  7. package/dist/botframework-webchat-fluent-theme.js +1 -1
  8. package/dist/botframework-webchat-fluent-theme.js.map +1 -1
  9. package/dist/botframework-webchat-fluent-theme.mjs +1 -1
  10. package/dist/botframework-webchat-fluent-theme.mjs.map +1 -1
  11. package/dist/botframework-webchat-fluent-theme.production.min.css.map +1 -1
  12. package/dist/botframework-webchat-fluent-theme.production.min.js +8 -8
  13. package/dist/botframework-webchat-fluent-theme.production.min.js.map +1 -1
  14. package/package.json +4 -4
  15. package/src/components/activity/ActivityDecorator.module.css +35 -17
  16. package/src/components/activity/ActivityDecorator.tsx +1 -1
  17. package/src/components/activity/CopilotMessageHeader.module.css +5 -3
  18. package/src/components/activity/CopilotMessageHeader.tsx +20 -12
  19. package/src/components/activity/index.ts +1 -0
  20. package/src/components/activity/private/isAIGeneratedActivity.ts +5 -0
  21. package/src/components/activity/private/useActivityAuthor.ts +16 -0
  22. package/src/components/activity/private/useActivityStyleOptions.ts +19 -0
  23. package/src/components/decorator/private/BorderFlair.module.css +4 -0
  24. package/src/components/decorator/private/BorderFlair.tsx +15 -2
  25. package/src/components/linerActivity/index.ts +2 -0
  26. package/src/components/linerActivity/private/LinerActivity.tsx +20 -0
  27. package/src/components/linerActivity/private/LinerMessageActivity.module.css +28 -0
  28. package/src/components/linerActivity/private/isLinerMessageActivity.ts +7 -0
  29. package/src/components/preChatActivity/PreChatMessageActivity.module.css +21 -7
  30. package/src/components/preChatActivity/PreChatMessageActivity.tsx +32 -26
  31. package/src/components/preChatActivity/StarterPromptsCardAction.module.css +55 -16
  32. package/src/components/preChatActivity/StarterPromptsCardAction.tsx +30 -24
  33. package/src/components/preChatActivity/StarterPromptsToolbar.tsx +23 -10
  34. package/src/components/sendBox/Attachments.tsx +5 -4
  35. package/src/components/sendBox/SendBox.module.css +7 -0
  36. package/src/components/sendBox/SendBox.tsx +31 -17
  37. package/src/components/sendBox/TextArea.tsx +50 -30
  38. package/src/components/sendBox/Toolbar.module.css +7 -1
  39. package/src/components/sendBox/Toolbar.tsx +17 -7
  40. package/src/components/suggestedActions/AccessibleButton.tsx +15 -13
  41. package/src/components/suggestedActions/SuggestedAction.module.css +8 -7
  42. package/src/components/suggestedActions/SuggestedAction.tsx +7 -4
  43. package/src/components/suggestedActions/SuggestedActions.tsx +3 -2
  44. package/src/components/theme/Theme.module.css +25 -8
  45. package/src/private/FluentThemeProvider.tsx +7 -1
  46. 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 || '') } : { __html: '' }),
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
- disabled={!messageBackAction}
41
- onClick={handleClick}
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
- {messageBackAction && (
45
- <React.Fragment>
46
- <div className={classNames['pre-chat-message-activity__card-action-title']}>
47
- {'title' in messageBackAction && messageBackAction.title}
48
- </div>
49
- {'image' in messageBackAction && messageBackAction.image && (
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, { memo, type ReactNode } from '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 StarterPrompts = ({ cardActions, children, className }: Props) => {
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
- {children ||
24
+ {uiState === 'blueprint' ? (
25
+ <Fragment>
26
+ <StarterPromptsCardAction />
27
+ <StarterPromptsCardAction />
28
+ <StarterPromptsCardAction />
29
+ </Fragment>
30
+ ) : (
22
31
  cardActions
23
- .filter<
24
- DirectLineCardAction & { type: 'messageBack' }
25
- >((card: DirectLineCardAction): card is DirectLineCardAction & { type: 'messageBack' } => card.type === 'messageBack')
26
- .map(cardAction => <StarterPromptsCardAction key={cardAction.text} messageBackAction={cardAction} />)}
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
- StarterPrompts.displayName = 'StarterPrompts';
45
+ StarterPromptsToolbar.displayName = 'StarterPromptsToolbar';
33
46
 
34
- export default memo(StarterPrompts);
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 styles from './Attachments.module.css';
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 isMessageLengthExceeded = !!maxMessageLength && message.length > maxMessageLength;
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 errorMessageId = useUniqueId('sendbox__error-message-id');
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 {...aria} className={cx(classNames['sendbox'], props.className)} onSubmit={handleFormSubmit}>
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={telephoneKeypadShown}
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
- {!telephoneKeypadShown && maxMessageLength && isFinite(maxMessageLength) && (
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 || telephoneKeypadShown}
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
- <div
54
- className={cx(
55
- classNames['sendbox__text-area-doppelganger'],
56
- classNames['sendbox__text-area-shared'],
57
- classNames['sendbox__text-area-input--scroll']
58
- )}
59
- >
60
- {props.value || props.placeholder}{' '}
61
- </div>
62
- <textarea
63
- aria-label={props['aria-label']}
64
- className={cx(
65
- classNames['sendbox__text-area-input'],
66
- classNames['sendbox__text-area-shared'],
67
- classNames['sendbox__text-area-input--scroll']
68
- )}
69
- data-testid={props['data-testid']}
70
- onInput={props.onInput}
71
- onKeyDown={handleKeyDown}
72
- placeholder={props.placeholder}
73
- ref={ref}
74
- rows={props.startRows ?? 1}
75
- // eslint-disable-next-line no-magic-numbers
76
- tabIndex={props.hidden ? -1 : undefined}
77
- value={props.value}
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={props.disabled ? preventDefaultHandler : props.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 <div className={cx(classNames['sendbox__toolbar'], props.className)}>{props.children}</div>;
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
- 'aria-hidden'?: boolean;
8
- children?: ReactNode;
9
- disabled?: boolean;
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
- ({ 'aria-hidden': ariaHidden, children, disabled, onClick, tabIndex, ...props }, forwardedRef) => {
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' : 'false'}
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
- tabIndex={tabIndex}
46
- {...(disabled && {
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: 8px;
5
- border: 1px solid var(--webchat-colorBrandStroke2);
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-size: 12px;
10
- gap: 4px;
11
- padding: 4px 8px 4px;
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 .15s ease-out;
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: 12px;
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
- import styles from './SuggestedAction.module.css';
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 { useDisabled, useFocus, usePerformCardAction, useScrollToEnd, useStyleSet, useSuggestedActions } = hooks;
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 [disabled] = useDisabled();
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
- disabled={disabled}
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
- {!!props.children && !!React.Children.count(props.children) && props.children}
40
+ {uiState !== 'blueprint' && props.children}
40
41
  </div>
41
42
  );
42
43
  }