botframework-webchat-fluent-theme 4.18.1-main.20240831.f4058ce → 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 (43) 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/CopilotMessageHeader.module.css +5 -3
  17. package/src/components/activity/CopilotMessageHeader.tsx +20 -12
  18. package/src/components/activity/index.ts +1 -0
  19. package/src/components/activity/private/isAIGeneratedActivity.ts +5 -0
  20. package/src/components/activity/private/useActivityAuthor.ts +16 -0
  21. package/src/components/activity/private/useActivityStyleOptions.ts +19 -0
  22. package/src/components/decorator/private/BorderFlair.module.css +4 -0
  23. package/src/components/decorator/private/BorderFlair.tsx +15 -2
  24. package/src/components/linerActivity/private/LinerActivity.tsx +1 -1
  25. package/src/components/linerActivity/private/LinerMessageActivity.module.css +4 -0
  26. package/src/components/preChatActivity/PreChatMessageActivity.module.css +21 -7
  27. package/src/components/preChatActivity/PreChatMessageActivity.tsx +32 -26
  28. package/src/components/preChatActivity/StarterPromptsCardAction.module.css +55 -16
  29. package/src/components/preChatActivity/StarterPromptsCardAction.tsx +30 -24
  30. package/src/components/preChatActivity/StarterPromptsToolbar.tsx +23 -10
  31. package/src/components/sendBox/Attachments.tsx +5 -4
  32. package/src/components/sendBox/SendBox.module.css +7 -0
  33. package/src/components/sendBox/SendBox.tsx +31 -17
  34. package/src/components/sendBox/TextArea.tsx +50 -30
  35. package/src/components/sendBox/Toolbar.module.css +7 -1
  36. package/src/components/sendBox/Toolbar.tsx +17 -7
  37. package/src/components/suggestedActions/AccessibleButton.tsx +15 -13
  38. package/src/components/suggestedActions/SuggestedAction.module.css +8 -7
  39. package/src/components/suggestedActions/SuggestedAction.tsx +7 -4
  40. package/src/components/suggestedActions/SuggestedActions.tsx +3 -2
  41. package/src/components/theme/Theme.module.css +19 -8
  42. package/src/private/FluentThemeProvider.tsx +2 -0
  43. package/src/testIds.ts +2 -0
@@ -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
  }
@@ -22,6 +22,9 @@
22
22
  --webchat-colorNeutralBackground5: var(--colorNeutralBackground5, #ebebeb);
23
23
  --webchat-colorNeutralBackground6: var(--colorNeutralBackground6, #e6e6e6);
24
24
 
25
+ --webchat-colorNeutralStencil1: var(--colorNeutralStencil1, #e6e6e6); /* #575757 for dark mode */
26
+ --webchat-colorNeutralStencil2: var(--colorNeutralStencil2, #fafafa); /* #333333 for dark mode */
27
+
25
28
  --webchat-colorTransparentBackground: var(--colorTransparentBackground, rgba(0, 0, 0, 0.4));
26
29
 
27
30
  --webchat-colorNeutralStrokeDisabled: var(--colorNeutralStrokeDisabled, #e0e0e0);
@@ -257,18 +260,26 @@
257
260
  display: none;
258
261
  }
259
262
 
260
- :global(.webchat__bubble)::after, :global(.pre-chat-message-activity)::after {
261
- content: '';
262
- position: absolute;
263
- inset: -2px;
264
- border: var(--webchat-strokeWidthThick) solid var(--webchat-colorStrokeFocus2);
263
+ :global(.webchat__bubble)::after,
264
+ :global(.pre-chat-message-activity)::after {
265
265
  border-radius: var(--webchat__bubble--border-radius);
266
+ content: '';
267
+ inset: 0;
268
+ outline-offset: 0;
269
+ outline: var(--webchat-strokeWidthThick) solid var(--webchat-colorStrokeFocus2);
266
270
  pointer-events: none;
271
+ position: absolute;
267
272
  }
268
273
 
269
274
  :global(.pre-chat-message-activity)::after {
270
- inset: 0;
271
- border-radius: var(--webchat-borderRadiusSmall);
275
+ border-radius: var(--webchat-borderRadiusMedium);
276
+ outline-offset: -3px;
277
+ }
278
+
279
+ :global(.liner-message-activity__text) {
280
+ border-radius: 20px;
281
+ outline-offset: 4px;
282
+ outline: var(--webchat-strokeWidthThick) solid var(--webchat-colorStrokeFocus2);
272
283
  }
273
284
  }
274
285
 
@@ -356,7 +367,7 @@
356
367
  outline-offset: calc(var(--webchat-strokeWidthThick) * -1);
357
368
  }
358
369
 
359
- &:disabled {
370
+ &[aria-disabled="true"] {
360
371
  background: var(--webchat-colorNeutralBackgroundDisabled);
361
372
  border: var(--webchat-strokeWidthThin) solid var(--webchat-colorNeutralStrokeDisabled);
362
373
  color: var(--webchat-colorNeutralForegroundDisabled);
@@ -21,9 +21,11 @@ const activityMiddleware: readonly ActivityMiddleware[] = Object.freeze([
21
21
  (...args) => {
22
22
  const activity = args[0]?.activity;
23
23
 
24
+ // TODO: Should show pre-chat only when it is the very first message in the chat history.
24
25
  if (isPreChatMessageActivity(activity)) {
25
26
  return () => <PreChatMessageActivity activity={activity} />;
26
27
  }
28
+
27
29
  if (isLinerMessageActivity(activity)) {
28
30
  return () => <LinerMessageActivity activity={activity} />;
29
31
  }
package/src/testIds.ts CHANGED
@@ -1,7 +1,9 @@
1
1
  const testIds = {
2
2
  preChatMessageActivityStarterPromptsCardAction: 'pre-chat message activity starter prompts card action',
3
+ sendBoxContainer: 'send box container',
3
4
  sendBoxDropZone: 'send box drop zone',
4
5
  sendBoxSendButton: 'send box send button',
6
+ sendBoxSuggestedAction: 'send box suggested action',
5
7
  sendBoxTextBox: 'send box text area',
6
8
  sendBoxTelephoneKeypadButton1: `send box telephone keypad button 1`,
7
9
  sendBoxTelephoneKeypadButton2: `send box telephone keypad button 2`,