botframework-webchat-fluent-theme 4.18.1-main.20240820.2dbeed3 → 4.18.1-main.20240830.4534802

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 (37) hide show
  1. package/dist/botframework-webchat-fluent-theme.css.map +1 -1
  2. package/dist/botframework-webchat-fluent-theme.development.css.map +1 -1
  3. package/dist/botframework-webchat-fluent-theme.development.js +8 -8
  4. package/dist/botframework-webchat-fluent-theme.development.js.map +1 -1
  5. package/dist/botframework-webchat-fluent-theme.js +1 -1
  6. package/dist/botframework-webchat-fluent-theme.js.map +1 -1
  7. package/dist/botframework-webchat-fluent-theme.mjs +1 -1
  8. package/dist/botframework-webchat-fluent-theme.mjs.map +1 -1
  9. package/dist/botframework-webchat-fluent-theme.production.min.css.map +1 -1
  10. package/dist/botframework-webchat-fluent-theme.production.min.js +8 -8
  11. package/dist/botframework-webchat-fluent-theme.production.min.js.map +1 -1
  12. package/package.json +4 -4
  13. package/src/components/decorator/index.ts +1 -1
  14. package/src/components/decorator/private/BorderFlair.module.css +548 -0
  15. package/src/components/decorator/private/BorderFlair.tsx +17 -0
  16. package/src/components/decorator/private/BorderLoader.module.css +49 -0
  17. package/src/components/decorator/private/BorderLoader.tsx +19 -0
  18. package/src/components/decorator/private/Decorator.tsx +20 -0
  19. package/src/components/preChatActivity/PreChatMessageActivity.module.css +4 -0
  20. package/src/components/preChatActivity/PreChatMessageActivity.tsx +24 -5
  21. package/src/components/preChatActivity/StarterPromptsCardAction.module.css +26 -2
  22. package/src/components/preChatActivity/StarterPromptsCardAction.tsx +26 -21
  23. package/src/components/preChatActivity/StarterPromptsToolbar.tsx +9 -10
  24. package/src/components/sendBox/ErrorMessage.tsx +15 -4
  25. package/src/components/sendBox/SendBox.tsx +16 -5
  26. package/src/components/sendBox/private/useSubmitError.ts +17 -4
  27. package/src/components/suggestedActions/SuggestedActions.tsx +0 -1
  28. package/src/components/theme/Theme.module.css +5 -0
  29. package/src/external.umd/botframework-webchat-component/internal.ts +1 -0
  30. package/src/private/FluentThemeProvider.tsx +2 -1
  31. package/src/components/decorator/Decorator.module.css +0 -3
  32. package/src/components/decorator/Decorator.tsx +0 -27
  33. package/src/components/decorator/Flair.module.css +0 -4
  34. package/src/components/decorator/Flair.tsx +0 -12
  35. package/src/components/decorator/Loader.module.css +0 -3
  36. package/src/components/decorator/Loader.tsx +0 -17
  37. /package/src/external.umd/{botframework-webchat-component.ts → botframework-webchat-component/index.ts} +0 -0
@@ -1,9 +1,12 @@
1
+ /* eslint-disable react/no-danger */
1
2
  import { hooks } from 'botframework-webchat-component';
2
- import { type WebChatActivity } from 'botframework-webchat-core';
3
- import React, { memo, useMemo } from 'react';
3
+ import cx from 'classnames';
4
+ import { getOrgSchemaMessage, type WebChatActivity } from 'botframework-webchat-core';
5
+ import React, { Fragment, memo, useMemo } from 'react';
4
6
  import { useStyles } from '../../styles/index.js';
5
7
  import styles from './PreChatMessageActivity.module.css';
6
8
  import StarterPromptsToolbar from './StarterPromptsToolbar.js';
9
+ import StarterPromptsCardAction from './StarterPromptsCardAction.js';
7
10
 
8
11
  type Props = Readonly<{ activity: WebChatActivity & { type: 'message' } }>;
9
12
 
@@ -18,14 +21,30 @@ const PreChatMessageActivity = ({ activity }: Props) => {
18
21
  [activity.text, renderMarkdownAsHTML]
19
22
  );
20
23
 
24
+ const entity = getOrgSchemaMessage(activity?.entities || []);
25
+ const isPlaceHolder = entity?.keywords?.includes('PreChatMessage') && entity.creativeWorkStatus === 'Placeholder';
26
+
21
27
  return (
22
28
  <div className={classNames['pre-chat-message-activity']}>
23
- {/* eslint-disable-next-line react/no-danger */}
24
- <div className={classNames['pre-chat-message-activity__body']} dangerouslySetInnerHTML={html} />
29
+ <div
30
+ className={cx(
31
+ classNames['pre-chat-message-activity__body'],
32
+ isPlaceHolder && classNames['pre-chat-message-activity__body--placeholder']
33
+ )}
34
+ dangerouslySetInnerHTML={html}
35
+ />
25
36
  <StarterPromptsToolbar
26
37
  cardActions={activity.suggestedActions?.actions || []}
27
38
  className={classNames['pre-chat-message-activity__toolbar']}
28
- />
39
+ >
40
+ {isPlaceHolder && (
41
+ <Fragment>
42
+ <StarterPromptsCardAction />
43
+ <StarterPromptsCardAction />
44
+ <StarterPromptsCardAction />
45
+ </Fragment>
46
+ )}
47
+ </StarterPromptsToolbar>
29
48
  </div>
30
49
  );
31
50
  };
@@ -22,14 +22,18 @@
22
22
  }
23
23
 
24
24
  &:disabled {
25
+ cursor: default;
26
+ }
27
+
28
+ &:disabled:not(:empty) {
25
29
  background-color: var(--webchat-colorNeutralBackgroundDisabled);
26
30
  }
27
31
 
28
- &:hover {
32
+ &:hover:not(:disabled) {
29
33
  background-color: var(--webchat-colorNeutralBackground1Hover);
30
34
  }
31
35
 
32
- &:active {
36
+ &:active:not(:disabled) {
33
37
  background-color: var(--webchat-colorNeutralBackground1Pressed);
34
38
  }
35
39
 
@@ -37,6 +41,26 @@
37
41
  outline: solid 2px var(--webchat-colorStrokeFocus2);
38
42
  outline-offset: -2px;
39
43
  }
44
+
45
+ &:empty::before {
46
+ content: '';
47
+ display: block;
48
+ width: 66%;
49
+ height: 18px;
50
+ background-color: var(--webchat-colorNeutralBackground6);
51
+ border-radius: 18px;
52
+ opacity: 1;
53
+ }
54
+
55
+ &:empty::after {
56
+ content: '';
57
+ display: block;
58
+ width: 100%;
59
+ height: 16px;
60
+ background-color: var(--webchat-colorNeutralBackground6);
61
+ border-radius: 16px;
62
+ opacity: 0.8;
63
+ }
40
64
  }
41
65
 
42
66
  :global(.webchat-fluent) .pre-chat-message-activity__card-action-image {
@@ -12,18 +12,18 @@ const { MonochromeImageMasker } = Components;
12
12
 
13
13
  type Props = Readonly<{
14
14
  className?: string | undefined;
15
- messageBackAction: DirectLineCardAction & { type: 'messageBack' };
15
+ messageBackAction?: (DirectLineCardAction & { type: 'messageBack' }) | undefined;
16
16
  }>;
17
17
 
18
- const StarterPromptAction = ({ className, messageBackAction }: Props) => {
18
+ const StarterPromptsCardAction = ({ className, messageBackAction }: Props) => {
19
19
  const [_, setSendBoxValue] = useSendBoxValue();
20
20
  const classNames = useStyles(styles);
21
21
  const focus = useFocus();
22
- const inputTextRef = useRefFrom(messageBackAction.displayText || messageBackAction.text || '');
22
+ const inputTextRef = useRefFrom(messageBackAction?.displayText || messageBackAction?.text || '');
23
23
  const renderMarkdownAsHTML = useRenderMarkdownAsHTML('message activity');
24
24
  const subtitleHTML = useMemo(
25
- () => (renderMarkdownAsHTML ? { __html: renderMarkdownAsHTML(messageBackAction.text || '') } : { __html: '' }),
26
- [messageBackAction.text, renderMarkdownAsHTML]
25
+ () => (renderMarkdownAsHTML ? { __html: renderMarkdownAsHTML(messageBackAction?.text || '') } : { __html: '' }),
26
+ [messageBackAction?.text, renderMarkdownAsHTML]
27
27
  );
28
28
 
29
29
  const handleClick = useCallback(() => {
@@ -37,28 +37,33 @@ const StarterPromptAction = ({ className, messageBackAction }: Props) => {
37
37
  <button
38
38
  className={cx(className, classNames['pre-chat-message-activity__card-action-box'])}
39
39
  data-testid={testIds.preChatMessageActivityStarterPromptsCardAction}
40
+ disabled={!messageBackAction}
40
41
  onClick={handleClick}
41
42
  type="button"
42
43
  >
43
- <div className={classNames['pre-chat-message-activity__card-action-title']}>
44
- {'title' in messageBackAction && messageBackAction.title}
45
- </div>
46
- {'image' in messageBackAction && messageBackAction.image && (
47
- <MonochromeImageMasker
48
- className={classNames['pre-chat-message-activity__card-action-image']}
49
- src={messageBackAction.image}
50
- />
51
- // <img className="pre-chat-message-activity__card-action-image" src={messageBackAction.image} />
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>
52
62
  )}
53
- <div
54
- className={classNames['pre-chat-message-activity__card-action-subtitle']}
55
- // eslint-disable-next-line react/no-danger
56
- dangerouslySetInnerHTML={subtitleHTML}
57
- />
58
63
  </button>
59
64
  );
60
65
  };
61
66
 
62
- StarterPromptAction.displayName = 'StarterPromptAction';
67
+ StarterPromptsCardAction.displayName = 'StarterPromptsCardAction';
63
68
 
64
- export default memo(StarterPromptAction);
69
+ export default memo(StarterPromptsCardAction);
@@ -1,6 +1,6 @@
1
1
  import { type DirectLineCardAction } from 'botframework-webchat-core';
2
2
  import cx from 'classnames';
3
- import React, { memo } from 'react';
3
+ import React, { memo, type ReactNode } from 'react';
4
4
  import { useStyles } from '../../styles/index.js';
5
5
  import StarterPromptsCardAction from './StarterPromptsCardAction.js';
6
6
  import styles from './StarterPromptsToolbar.module.css';
@@ -8,23 +8,22 @@ import styles from './StarterPromptsToolbar.module.css';
8
8
  type Props = Readonly<{
9
9
  cardActions: readonly DirectLineCardAction[];
10
10
  className?: string | undefined;
11
+ children?: ReactNode | undefined;
11
12
  }>;
12
13
 
13
- const StarterPrompts = ({ cardActions, className }: Props) => {
14
+ const StarterPrompts = ({ cardActions, children, className }: Props) => {
14
15
  const classNames = useStyles(styles);
15
16
 
16
17
  return (
17
18
  // TODO: Accessibility-wise, this should be role="toolbar" with keyboard navigation.
18
19
  <div className={cx(className, classNames['pre-chat-message-activity__card-action-toolbar'])}>
19
20
  <div className={classNames['pre-chat-message-activity__card-action-toolbar-grid']}>
20
- {cardActions
21
- .filter<DirectLineCardAction & { type: 'messageBack' }>(
22
- (card: DirectLineCardAction): card is DirectLineCardAction & { type: 'messageBack' } =>
23
- card.type === 'messageBack'
24
- )
25
- .map(cardAction => (
26
- <StarterPromptsCardAction key={cardAction.text} messageBackAction={cardAction} />
27
- ))}
21
+ {children ||
22
+ 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} />)}
28
27
  </div>
29
28
  </div>
30
29
  );
@@ -1,15 +1,26 @@
1
+ import { useLiveRegion } from 'botframework-webchat-component/internal';
1
2
  import React, { memo } from 'react';
2
- import styles from './ErrorMessage.module.css';
3
3
  import { useStyles } from '../../styles';
4
+ import styles from './ErrorMessage.module.css';
5
+
6
+ type ErrorMessageProps = Readonly<{
7
+ error?: string | undefined;
8
+ id: string;
9
+ }>;
4
10
 
5
- function ErrorMessage(props: Readonly<{ id: string; error?: string | undefined }>) {
11
+ function ErrorMessage({ error, id }: ErrorMessageProps) {
6
12
  const classNames = useStyles(styles);
13
+
14
+ useLiveRegion(() => error && <div className="sendbox__error-message__status">{error}</div>, [error]);
15
+
7
16
  return (
8
17
  // eslint-disable-next-line react/forbid-dom-props
9
- <span className={classNames['sendbox__error-message']} id={props.id} role="alert">
10
- {props.error}
18
+ <span className={classNames['sendbox__error-message']} id={id}>
19
+ {error}
11
20
  </span>
12
21
  );
13
22
  }
14
23
 
24
+ ErrorMessage.displayName = 'ErrorMessage';
25
+
15
26
  export default memo(ErrorMessage);
@@ -50,7 +50,7 @@ function SendBox(props: Props) {
50
50
  const sendMessage = useSendMessage();
51
51
  const makeThumbnail = useMakeThumbnail();
52
52
  const errorMessageId = useUniqueId('sendbox__error-message-id');
53
- const [errorRef, errorMessage] = useSubmitError({ message, attachments });
53
+ const [errorMessage, commitLatestError] = useSubmitError({ message, attachments });
54
54
  const [telephoneKeypadShown] = useTelephoneKeypadShown();
55
55
  const setFocus = useFocus();
56
56
 
@@ -133,8 +133,9 @@ function SendBox(props: Props) {
133
133
  const handleFormSubmit: FormEventHandler<HTMLFormElement> = useCallback(
134
134
  event => {
135
135
  event.preventDefault();
136
+ const error = commitLatestError();
136
137
 
137
- if (errorRef.current !== 'empty' && !isMessageLengthExceeded) {
138
+ if (error !== 'empty' && !isMessageLengthExceeded) {
138
139
  sendMessage(messageRef.current, undefined, { attachments: attachmentsRef.current });
139
140
 
140
141
  setMessage('');
@@ -143,7 +144,16 @@ function SendBox(props: Props) {
143
144
 
144
145
  setFocus('sendBox');
145
146
  },
146
- [attachmentsRef, messageRef, sendMessage, setAttachments, setMessage, isMessageLengthExceeded, errorRef, setFocus]
147
+ [
148
+ commitLatestError,
149
+ isMessageLengthExceeded,
150
+ setFocus,
151
+ sendMessage,
152
+ setMessage,
153
+ messageRef,
154
+ attachmentsRef,
155
+ setAttachments
156
+ ]
147
157
  );
148
158
 
149
159
  const handleTelephoneKeypadButtonClick = useCallback(
@@ -157,8 +167,9 @@ function SendBox(props: Props) {
157
167
  const aria = {
158
168
  'aria-invalid': 'false' as const,
159
169
  ...(errorMessage && {
160
- 'aria-invalid': 'true' as const,
161
- 'aria-errormessage': errorMessageId
170
+ 'aria-describedby': errorMessageId,
171
+ 'aria-errormessage': errorMessageId,
172
+ 'aria-invalid': 'true' as const
162
173
  })
163
174
  };
164
175
 
@@ -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
 
@@ -24,7 +24,6 @@ function SuggestedActionStackedOrFlowContainer(
24
24
  return (
25
25
  <div
26
26
  aria-label={props['aria-label']}
27
- aria-live="polite"
28
27
  aria-orientation="vertical"
29
28
  className={cx(
30
29
  classNames['suggested-actions'],
@@ -20,6 +20,7 @@
20
20
  --webchat-colorNeutralBackground3: var(--colorNeutralBackground3, #f5f5f5);
21
21
  --webchat-colorNeutralBackground4: var(--colorNeutralBackground4, #f0f0f0);
22
22
  --webchat-colorNeutralBackground5: var(--colorNeutralBackground5, #ebebeb);
23
+ --webchat-colorNeutralBackground6: var(--colorNeutralBackground6, #e6e6e6);
23
24
 
24
25
  --webchat-colorTransparentBackground: var(--colorTransparentBackground, rgba(0, 0, 0, 0.4));
25
26
 
@@ -167,6 +168,10 @@
167
168
  var(--webchat-spacingVerticalNone)
168
169
  var(--webchat-spacingHorizontalMNudge)
169
170
  var(--webchat-spacingHorizontalMNudge);
171
+
172
+ --webchat__border-animation--color-1: #464FEB;
173
+ --webchat__border-animation--color-2: #47CFFA;
174
+ --webchat__border-animation--color-3: #B47CF8;
170
175
  }
171
176
 
172
177
  :global(.webchat-fluent).theme.variant-copilot :global(.webchat__css-custom-properties) {
@@ -0,0 +1 @@
1
+ module.exports = (globalThis as any).WebChat.internal;
@@ -8,6 +8,7 @@ import { PrimarySendBox } from '../components/sendBox';
8
8
  import { TelephoneKeypadProvider } from '../components/telephoneKeypad';
9
9
  import { WebChatTheme } from '../components/theme';
10
10
  import VariantComposer, { VariantList } from './VariantComposer';
11
+ import { FluentThemeDecorator } from '../components/decorator';
11
12
 
12
13
  const { ThemeProvider } = Components;
13
14
 
@@ -38,7 +39,7 @@ const FluentThemeProvider = ({ children, variant = 'fluent' }: Props) => (
38
39
  <WebChatTheme>
39
40
  <TelephoneKeypadProvider>
40
41
  <ThemeProvider activityMiddleware={activityMiddleware} sendBoxMiddleware={sendBoxMiddleware}>
41
- {children}
42
+ <FluentThemeDecorator>{children}</FluentThemeDecorator>
42
43
  </ThemeProvider>
43
44
  </TelephoneKeypadProvider>
44
45
  </WebChatTheme>
@@ -1,3 +0,0 @@
1
- :global(.webchat-fluent-decorator).decorator {
2
- display: contents;
3
- }
@@ -1,27 +0,0 @@
1
- import { DecoratorComposer, type DecoratorMiddleware } from 'botframework-webchat-api/decorator';
2
- import cx from 'classnames';
3
- import React, { memo, type ReactNode } from 'react';
4
-
5
- import { useStyles } from '../../styles';
6
- import styles from './Decorator.module.css';
7
- import Flair from './Flair';
8
- import Loader from './Loader';
9
-
10
- export const rootClassName = 'webchat-fluent-decorator';
11
-
12
- const middleware: DecoratorMiddleware[] = [
13
- init => init === 'activity border' && (next => request => (request.state === 'completion' ? Flair : next(request))),
14
- init => init === 'activity border' && (next => request => (request.state === 'informative' ? Loader : next(request)))
15
- ];
16
-
17
- function WebChatDecorator(props: Readonly<{ readonly children?: ReactNode | undefined }>) {
18
- const classNames = useStyles(styles);
19
-
20
- return (
21
- <div className={cx(rootClassName, classNames['decorator'])}>
22
- <DecoratorComposer middleware={middleware}>{props.children}</DecoratorComposer>
23
- </div>
24
- );
25
- }
26
-
27
- export default memo(WebChatDecorator);
@@ -1,4 +0,0 @@
1
- :global(.webchat-fluent-decorator) .flair {
2
- border-radius: inherit;
3
- border: solid 2px red;
4
- }
@@ -1,12 +0,0 @@
1
- import React, { memo, type ReactNode } from 'react';
2
-
3
- import { useStyles } from '../../styles';
4
- import styles from './Flair.module.css';
5
-
6
- function Flair({ children }: Readonly<{ children?: ReactNode | undefined }>) {
7
- const classNames = useStyles(styles);
8
-
9
- return <div className={classNames['flair']}>{children}</div>;
10
- }
11
-
12
- export default memo(Flair);
@@ -1,3 +0,0 @@
1
- :global(.webchat-fluent-decorator) .loader {
2
- border-bottom: solid 4px blue;
3
- }
@@ -1,17 +0,0 @@
1
- import React, { memo, type ReactNode } from 'react';
2
-
3
- import { useStyles } from '../../styles';
4
- import styles from './Loader.module.css';
5
-
6
- function Loader({ children }: Readonly<{ children?: ReactNode | undefined }>) {
7
- const classNames = useStyles(styles);
8
-
9
- return (
10
- <React.Fragment>
11
- {children}
12
- <div className={classNames['loader']} />
13
- </React.Fragment>
14
- );
15
- }
16
-
17
- export default memo(Loader);