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.
Files changed (96) hide show
  1. package/dist/botframework-webchat-fluent-theme.css.map +1 -1
  2. package/dist/botframework-webchat-fluent-theme.d.mts +23 -5
  3. package/dist/botframework-webchat-fluent-theme.d.ts +23 -5
  4. package/dist/botframework-webchat-fluent-theme.development.css.map +1 -1
  5. package/dist/botframework-webchat-fluent-theme.development.js +11 -1
  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 +11 -1
  13. package/dist/botframework-webchat-fluent-theme.production.min.js.map +1 -1
  14. package/package.json +27 -26
  15. package/src/bundle.ts +2 -0
  16. package/src/components/activity/ActivityDecorator.module.css +432 -0
  17. package/src/components/activity/ActivityDecorator.tsx +26 -0
  18. package/src/components/activity/ActivityLoader.module.css +10 -0
  19. package/src/components/activity/ActivityLoader.tsx +21 -0
  20. package/src/components/activity/CopilotMessageHeader.module.css +38 -0
  21. package/src/components/activity/CopilotMessageHeader.tsx +49 -0
  22. package/src/components/activity/index.ts +2 -0
  23. package/src/components/activity/private/isAIGeneratedActivity.ts +5 -0
  24. package/src/components/activity/private/useActivityAuthor.ts +16 -0
  25. package/src/components/activity/private/useActivityStyleOptions.ts +19 -0
  26. package/src/components/assets/AssetComposer.tsx +37 -0
  27. package/src/components/assets/AssetName.ts +1 -0
  28. package/src/components/assets/SlidingDots.tsx +61 -0
  29. package/src/components/assets/private/Context.ts +24 -0
  30. package/src/components/assets/private/useAssetURL.ts +12 -0
  31. package/src/components/assets/private/useContext.ts +8 -0
  32. package/src/components/dropZone/DropZone.module.css +3 -4
  33. package/src/components/dropZone/DropZone.tsx +41 -6
  34. package/src/components/linerActivity/index.ts +2 -0
  35. package/src/components/linerActivity/private/LinerActivity.tsx +20 -0
  36. package/src/components/linerActivity/private/LinerMessageActivity.module.css +28 -0
  37. package/src/components/linerActivity/private/isLinerMessageActivity.ts +7 -0
  38. package/src/components/preChatActivity/PreChatMessageActivity.module.css +56 -0
  39. package/src/components/preChatActivity/PreChatMessageActivity.tsx +60 -0
  40. package/src/components/preChatActivity/StarterPromptsCardAction.module.css +126 -0
  41. package/src/components/preChatActivity/StarterPromptsCardAction.tsx +75 -0
  42. package/src/components/preChatActivity/StarterPromptsToolbar.module.css +18 -0
  43. package/src/components/preChatActivity/StarterPromptsToolbar.tsx +47 -0
  44. package/src/components/preChatActivity/index.tsx +2 -0
  45. package/src/components/preChatActivity/isPreChatMessageActivity.ts +7 -0
  46. package/src/components/sendBox/Attachments.module.css +1 -1
  47. package/src/components/sendBox/Attachments.tsx +5 -4
  48. package/src/components/sendBox/ErrorMessage.tsx +15 -4
  49. package/src/components/sendBox/SendBox.module.css +21 -6
  50. package/src/components/sendBox/SendBox.tsx +95 -56
  51. package/src/components/sendBox/TextArea.module.css +27 -8
  52. package/src/components/sendBox/TextArea.tsx +60 -31
  53. package/src/components/sendBox/Toolbar.module.css +15 -7
  54. package/src/components/sendBox/Toolbar.tsx +17 -7
  55. package/src/components/sendBox/index.tsx +1 -1
  56. package/src/components/sendBox/private/useSubmitError.ts +17 -4
  57. package/src/components/sendBox/private/useTranscriptNavigation.ts +53 -0
  58. package/src/components/suggestedActions/AccessibleButton.tsx +15 -13
  59. package/src/components/suggestedActions/SuggestedAction.module.css +14 -13
  60. package/src/components/suggestedActions/SuggestedAction.tsx +7 -4
  61. package/src/components/suggestedActions/SuggestedActions.module.css +2 -3
  62. package/src/components/suggestedActions/SuggestedActions.tsx +49 -46
  63. package/src/components/telephoneKeypad/private/Button.module.css +2 -3
  64. package/src/components/telephoneKeypad/private/Button.tsx +1 -5
  65. package/src/components/telephoneKeypad/private/TelephoneKeypad.module.css +0 -1
  66. package/src/components/telephoneKeypad/private/TelephoneKeypad.tsx +4 -8
  67. package/src/components/telephoneKeypad/types.ts +1 -1
  68. package/src/components/theme/Theme.module.css +665 -15
  69. package/src/components/theme/Theme.tsx +4 -3
  70. package/src/components/typingIndicator/SlidingDotsTypingIndicator.module.css +12 -0
  71. package/src/components/typingIndicator/SlidingDotsTypingIndicator.tsx +19 -0
  72. package/src/env.d.ts +1 -7
  73. package/src/external.umd/botframework-webchat-api/decorator.ts +1 -0
  74. package/src/external.umd/botframework-webchat-component/decorator.ts +1 -0
  75. package/src/external.umd/botframework-webchat-component/index.ts +5 -0
  76. package/src/external.umd/botframework-webchat-component/internal.ts +1 -0
  77. package/src/icons/AddDocumentIcon.tsx +8 -16
  78. package/src/icons/AttachmentIcon.tsx +8 -16
  79. package/src/icons/InfoSmallIcon.tsx +7 -15
  80. package/src/icons/SendIcon.tsx +7 -15
  81. package/src/icons/TelephoneKeypadIcon.tsx +7 -15
  82. package/src/index.ts +2 -4
  83. package/src/private/FluentThemeProvider.tsx +91 -10
  84. package/src/private/VariantComposer.ts +29 -0
  85. package/src/private/createComposer.tsx +16 -0
  86. package/src/private/useVariants.ts +7 -0
  87. package/src/styles/createStyles.ts +5 -0
  88. package/src/styles/index.ts +3 -2
  89. package/src/styles/useStyles.ts +2 -19
  90. package/src/styles/useVariantClassName.ts +16 -0
  91. package/src/testIds.ts +3 -0
  92. package/src/tsconfig.json +12 -10
  93. package/src/types/PropsOf.ts +2 -5
  94. package/src/external.umd/botframework-webchat-component.ts +0 -4
  95. package/src/styles/injectStyle.ts +0 -9
  96. /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, { forwardRef, useCallback, type FormEventHandler, type KeyboardEventHandler } from '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
- [classNames['sendbox__text-area--hidden']]: props.hidden
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
- <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
- />
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
- > svg {
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="true"]):hover {
34
+ &:not([aria-disabled='true']):hover {
31
35
  color: var(--webchat-colorNeutralForeground2BrandHover);
32
36
  }
33
37
  }
34
- &:not([aria-disabled="true"]):active {
38
+
39
+ &:not([aria-disabled='true']):active {
35
40
  color: var(--webchat-colorNeutralForeground2BrandPressed);
36
41
  }
37
- &[aria-disabled="true"] {
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, &:last-child, &:only-child {
49
- display: none
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={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';
@@ -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 { RefObject, useMemo } from 'react';
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
- return useMemo<Readonly<[RefObject<SendError | undefined>, string | undefined]>>(
41
- () => Object.freeze([submitErrorRef, submitErrorRef.current && errorMessageStringMap.get(submitErrorRef.current)]),
42
- [errorMessageStringMap, submitErrorRef]
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
- '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,35 +1,36 @@
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
- &:not([aria-disabled="true"]):hover {
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="true"]):active {
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="true"] {
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: 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"
@@ -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
- {!!props.children && !!React.Children.count(props.children) && props.children}
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 = suggestedActions.map((cardAction, index) => {
55
- const { displayText, image, imageAltText, text, type, value } = cardAction as {
56
- displayText?: string;
57
- image?: string;
58
- imageAltText?: string;
59
- text?: string;
60
- type:
61
- | 'call'
62
- | 'downloadFile'
63
- | 'imBack'
64
- | 'messageBack'
65
- | 'openUrl'
66
- | 'playAudio'
67
- | 'playVideo'
68
- | 'postBack'
69
- | 'showImage'
70
- | 'signin';
71
- value?: { [key: string]: any } | string;
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
- if (!suggestedActions?.length) {
75
- return null;
76
- }
77
+ if (!suggestedActions?.length) {
78
+ return null;
79
+ }
77
80
 
78
- return (
79
- <SuggestedAction
80
- buttonText={computeSuggestedActionText(cardAction)}
81
- displayText={displayText}
82
- image={image}
83
- // Image alt text should use `imageAltText` field and fallback to `text` field.
84
- // https://github.com/microsoft/botframework-sdk/blob/main/specs/botframework-activity/botframework-activity.md#image-alt-text
85
- imageAlt={image && (imageAltText || text)}
86
- itemIndex={index}
87
- // eslint-disable-next-line react/no-array-index-key
88
- key={index}
89
- text={text}
90
- type={type}
91
- value={value}
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;