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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "botframework-webchat-fluent-theme",
3
- "version": "4.18.1-main.20240831.f4058ce",
3
+ "version": "4.18.1-main.20240911.3e47786",
4
4
  "description": "Fluent theme for Bot Framework Web Chat",
5
5
  "main": "./dist/botframework-webchat-fluent-theme.js",
6
6
  "types": "./dist/botframework-webchat-fluent-theme.d.ts",
@@ -69,9 +69,9 @@
69
69
  "typescript": "^5.4.5"
70
70
  },
71
71
  "dependencies": {
72
- "botframework-webchat-api": "4.18.1-main.20240831.f4058ce",
73
- "botframework-webchat-component": "4.18.1-main.20240831.f4058ce",
74
- "botframework-webchat-core": "4.18.1-main.20240831.f4058ce",
72
+ "botframework-webchat-api": "4.18.1-main.20240911.3e47786",
73
+ "botframework-webchat-component": "4.18.1-main.20240911.3e47786",
74
+ "botframework-webchat-core": "4.18.1-main.20240911.3e47786",
75
75
  "classnames": "2.5.1",
76
76
  "inject-meta-tag": "0.0.1",
77
77
  "math-random": "2.0.1",
@@ -30,11 +30,15 @@
30
30
  /* Decorator copilot variant */
31
31
  :global(.webchat-fluent) .activity-decorator.variant-copilot {
32
32
  --webchat__bubble--border-radius: var(--webchat-borderRadiusXLarge);
33
-
33
+
34
34
  &:not(:has(:global(.webchat__bubble--from-user))) {
35
- --webchat__bubble--block-padding: var(--webchat-spacingVerticalS) var(--webchat-spacingVerticalXXS);
36
- --webchat__bubble--border-radius: var(--webchat-borderRadiusSmall);
37
- --webchat__bubble--inline-padding: 32px var(--webchat-spacingHorizontalNone);
35
+ --webchat__bubble--block-padding: var(--webchat-spacingVerticalS) var(--webchat-spacingVerticalS);
36
+ --webchat__bubble--border-radius: var(--webchat-borderRadiusMedium);
37
+ --webchat__bubble--inline-padding: 34px var(--webchat-spacingHorizontalS);
38
+ --webchat__bubble--max-width: 100%;
39
+
40
+ display: block;
41
+ padding-inline-start: var(--webchat-spacingHorizontalM);
38
42
  }
39
43
 
40
44
  &:not(:has(:global(.webchat__bubble--from-user))) :global(.webchat__stacked-layout__status) {
@@ -51,12 +55,13 @@
51
55
 
52
56
  /* TODO: remove when we get decorators in and can place header directly to the bubble */
53
57
  &:not(:has(:global(.webchat__bubble--from-user))) :global(.webchat__stacked-layout .webchat__bubble .webchat__text-content) {
54
- padding-block: calc(var(--webchat-spacingVerticalS) + 20px) var(--webchat-spacingVerticalXXS);
58
+ padding-block: calc(var(--webchat-spacingVerticalMNudge) + 20px) var(--webchat-spacingVerticalS);
55
59
  }
56
60
 
57
61
  &:not(:has(:global(.webchat__bubble--from-user))) :global(.webchat__stacked-layout .webchat__bubble) {
58
- margin-block-start: -24px;
59
- margin-inline-start: -4px;
62
+ margin-block-start: -28px;
63
+ margin-inline-start: -10px;
64
+ width: var(--webchat__bubble--max-width);
60
65
  }
61
66
  }
62
67
 
@@ -73,6 +78,7 @@
73
78
 
74
79
  /* Stacked layout which has message bubble */
75
80
  :global(.webchat-fluent) .activity-decorator :global(.webchat__stacked-layout .webchat__stacked-layout__content:has(.webchat__bubble)) {
81
+ max-width: 100%;
76
82
  overflow: visible;
77
83
  }
78
84
 
@@ -84,7 +90,7 @@
84
90
 
85
91
  /* Message bubble */
86
92
  :global(.webchat-fluent) .activity-decorator :global(.webchat__stacked-layout .webchat__bubble) {
87
- max-width: var(--webchat__bubble--max-width);
93
+ max-width: 100%;
88
94
  overflow: visible;
89
95
  }
90
96
 
@@ -93,9 +99,11 @@
93
99
  background-color: var(--webchat__bubble--background-color);
94
100
  border-radius: var(--webchat__bubble--border-radius);
95
101
  border-width: 0;
102
+ box-shadow: var(--webchat__bubble--box-shadow);
96
103
  box-sizing: border-box;
97
104
  color: var(--webchat-colorNeutralForeground1);
98
105
  min-height: var(--webchat__bubble--min-height);
106
+ max-width: var(--webchat__bubble--max-width);
99
107
  }
100
108
 
101
109
  /* Message bubble text content */
@@ -107,15 +115,20 @@
107
115
  padding-inline: var(--webchat__bubble--inline-padding);
108
116
  }
109
117
 
110
- /* Message bubble content pseudo */
111
- :global(.webchat-fluent) .activity-decorator :global(.webchat__stacked-layout .webchat__bubble .webchat__bubble__content)::before {
112
- border-radius: inherit;
113
- box-shadow: var(--webchat__bubble--box-shadow);
114
- content: '';
115
- inset: 0;
116
- isolation: isolate;
117
- pointer-events: none;
118
- position: absolute;
118
+ /* Message bubble attachment content */
119
+ :global(.webchat-fluent) .activity-decorator :global(.webchat__stacked-layout .webchat__bubble .webchat__fileContent__badge) {
120
+ cursor: default;
121
+ font-size: var(--webchat-fontSizeBase300);
122
+ line-height: var(--webchat-lineHeightBase300);
123
+
124
+ :global(.webchat__fileContent__fileName) {
125
+ color: var(--webchat-colorBrandForegroundLink);
126
+ font-family: inherit;
127
+ }
128
+ :global(.webchat__fileContent__size) {
129
+ color: var(--webchat-colorNeutralForeground2);
130
+ font-family: inherit;
131
+ }
119
132
  }
120
133
 
121
134
  /* Markdown links and citation links */
@@ -300,6 +313,11 @@
300
313
  width: 1em;
301
314
  }
302
315
 
316
+ /* It seems Copilot does not show per-item sensitivity label, i.e. "General", etc. */
317
+ :global(.webchat__link-definitions__list-item-badge) {
318
+ display: none;
319
+ }
320
+
303
321
  :global(.webchat__link-definitions__open-in-new-window-icon) {
304
322
  display: none;
305
323
  }
@@ -1,12 +1,14 @@
1
1
 
2
2
  :global(.webchat-fluent) .copilot-message-header {
3
3
  align-items: center;
4
+ box-sizing: border-box;
4
5
  cursor: default;
5
6
  display: flex;
6
7
  flex-wrap: nowrap;
7
8
  gap: var(--webchat-spacingHorizontalS);
8
- margin-inline-start: var(--webchat-spacingVerticalMNudge);
9
+ max-width: var(--webchat__bubble--max-width);
9
10
  padding-block-start: var(--webchat-spacingVerticalXS);
11
+ padding-inline: var(--webchat-spacingVerticalMNudge);
10
12
  /* TODO: remove when moved to the bubble */
11
13
  position: relative;
12
14
  z-index: 1;
@@ -23,7 +25,6 @@
23
25
  font-size: var(--webchat-fontSizeBase300);
24
26
  font-weight: var(--webchat-fontWeightSemibold);
25
27
  line-height: var(--webchat-lineHeightBase300);
26
- max-width: 240px;
27
28
  overflow: hidden;
28
29
  text-overflow: ellipsis;
29
30
  text-wrap: nowrap;
@@ -35,8 +36,9 @@
35
36
  border-radius: var(--webchat-borderRadiusMedium);
36
37
  color: var(--webchat-colorNeutralForeground3);
37
38
  display: flex;
39
+ flex: none;
38
40
  font-size: var(--webchat-fontSizeBase100);
39
41
  height: 20px;
40
42
  line-height: var(--webchat-lineHeightBase100);
41
- padding-inline: 4px;
43
+ padding-inline: var(--webchat-spacingHorizontalXS);
42
44
  }
@@ -1,31 +1,39 @@
1
- import { WebChatActivity, hooks } from 'botframework-webchat-component';
2
1
  import React, { memo, useMemo, type CSSProperties } from 'react';
2
+ import { WebChatActivity, hooks } from 'botframework-webchat-component';
3
+
4
+ import useActivityStyleOptions from './private/useActivityStyleOptions';
5
+ import isAIGeneratedActivity from './private/isAIGeneratedActivity';
6
+ import useActivityAuthor from './private/useActivityAuthor';
3
7
  import { useStyles } from '../../styles';
4
8
  import styles from './CopilotMessageHeader.module.css';
5
9
 
6
- const { useStyleOptions, useLocalizer } = hooks;
10
+ const { useLocalizer } = hooks;
7
11
 
8
12
  function CopilotMessageHeader({ activity }: Readonly<{ activity?: WebChatActivity | undefined }>) {
9
- const [{ botAvatarImage, botAvatarBackgroundColor }] = useStyleOptions();
13
+ const [{ botAvatarImage, botAvatarBackgroundColor }] = useActivityStyleOptions(activity);
10
14
  const classNames = useStyles(styles);
11
15
  const localize = useLocalizer();
12
- // TODO: how we determine the activity has ai-generated content
13
- const isAIGenerated = useMemo(() => !!activity, [activity]);
14
- const botTitle = activity?.from?.name;
16
+ const isAIGenerated = isAIGeneratedActivity(activity);
15
17
 
16
18
  const avatarStyle = useMemo(
17
19
  () => ({ '--background-color': botAvatarBackgroundColor }) as CSSProperties,
18
20
  [botAvatarBackgroundColor]
19
21
  );
20
22
 
23
+ const author = useActivityAuthor(activity);
24
+ const avatarImage = author?.image || botAvatarImage;
25
+ const botTitle = author?.name || activity?.from?.name;
26
+
21
27
  return (
22
28
  <div className={classNames['copilot-message-header']}>
23
- <img
24
- alt={localize('AVATAR_ALT', botTitle)}
25
- className={classNames['copilot-message-header__avatar']}
26
- src={botAvatarImage}
27
- style={avatarStyle}
28
- />
29
+ {avatarImage && (
30
+ <img
31
+ alt={localize('AVATAR_ALT', botTitle)}
32
+ className={classNames['copilot-message-header__avatar']}
33
+ src={avatarImage}
34
+ style={avatarStyle}
35
+ />
36
+ )}
29
37
  <span className={classNames['copilot-message-header__title']} title={botTitle}>
30
38
  {botTitle}
31
39
  </span>
@@ -1 +1,2 @@
1
1
  export { default as ActivityDecorator } from './ActivityDecorator';
2
+ export { default as useActivityAuthor } from './private/useActivityAuthor';
@@ -0,0 +1,5 @@
1
+ import { getOrgSchemaMessage, type WebChatActivity } from 'botframework-webchat-core';
2
+
3
+ export default function isAIGeneratedActivity(activity: undefined | WebChatActivity) {
4
+ return !!(activity && getOrgSchemaMessage(activity?.entities || [])?.keywords?.includes('AIGeneratedContent'));
5
+ }
@@ -0,0 +1,16 @@
1
+ import { useMemo } from 'react';
2
+ import { getOrgSchemaMessage, type WebChatActivity } from 'botframework-webchat-core';
3
+
4
+ export default function useActivityAuthor(activity?: WebChatActivity | undefined) {
5
+ return useMemo(() => {
6
+ const entity = getOrgSchemaMessage(activity?.entities || []);
7
+ return typeof entity?.author === 'string'
8
+ ? {
9
+ '@type': 'Person',
10
+ description: undefined,
11
+ image: undefined,
12
+ name: entity?.author
13
+ }
14
+ : entity?.author;
15
+ }, [activity]);
16
+ }
@@ -0,0 +1,19 @@
1
+ import { useMemo } from 'react';
2
+ import { hooks, type WebChatActivity } from 'botframework-webchat-component';
3
+ import { type StrictStyleOptions } from 'botframework-webchat-api';
4
+
5
+ const { useStyleOptions } = hooks;
6
+
7
+ export default function useActivityStyleOptions(activity?: WebChatActivity | undefined) {
8
+ const [styleOptions] = useStyleOptions();
9
+ return useMemo<readonly [Readonly<StrictStyleOptions>]>(
10
+ () =>
11
+ Object.freeze([
12
+ {
13
+ ...styleOptions,
14
+ ...activity?.channelData?.webChat?.styleOptions
15
+ }
16
+ ]),
17
+ [activity?.channelData?.webChat?.styleOptions, styleOptions]
18
+ );
19
+ }
@@ -123,6 +123,10 @@
123
123
  position: absolute;
124
124
  }
125
125
 
126
+ :global(.webchat-fluent) .border-flair--complete {
127
+ animation-play-state: paused;
128
+ }
129
+
126
130
  /* Firefox implementation */
127
131
  @supports (-moz-appearance: none) {
128
132
  @keyframes borderFlair-animation {
@@ -1,15 +1,28 @@
1
- import React, { Fragment, memo, type ReactNode } from 'react';
1
+ import React, { Fragment, memo, useCallback, useState, type ReactNode } from 'react';
2
+ import cx from 'classnames';
2
3
 
3
4
  import { useStyles } from '../../../styles';
4
5
  import styles from './BorderFlair.module.css';
5
6
 
6
7
  function BorderFlair({ children }: Readonly<{ children?: ReactNode | undefined }>) {
7
8
  const classNames = useStyles(styles);
9
+ const [isComplete, setComplete] = useState(false);
10
+
11
+ const handleAnimationEnd = useCallback(
12
+ event =>
13
+ (event.animationName === styles['borderAnimation-angle'] ||
14
+ event.animationName === styles['borderFlair-animation']) &&
15
+ setComplete(true),
16
+ []
17
+ );
8
18
 
9
19
  return (
10
20
  <Fragment>
11
21
  {children}
12
- <div className={classNames['border-flair']} />
22
+ <div
23
+ className={cx(classNames['border-flair'], isComplete && classNames['border-flair--complete'])}
24
+ onAnimationEnd={handleAnimationEnd}
25
+ />
13
26
  </Fragment>
14
27
  );
15
28
  }
@@ -10,7 +10,7 @@ const LinerMessageActivity = ({ activity }: Props) => {
10
10
 
11
11
  return (
12
12
  <div className={classNames['liner-message-activity']} role="separator">
13
- {activity.text}
13
+ <span className={classNames['liner-message-activity__text']}>{activity.text}</span>
14
14
  </div>
15
15
  );
16
16
  };
@@ -22,3 +22,7 @@
22
22
  min-width: 8px;
23
23
  }
24
24
  }
25
+
26
+ :global(.webchat-fluent) .liner-message-activity__text {
27
+ text-align: center;
28
+ }
@@ -9,30 +9,44 @@
9
9
  }
10
10
 
11
11
  :global(.webchat-fluent) .pre-chat-message-activity__body {
12
+ align-items: center;
13
+ display: flex;
14
+ flex-flow: column nowrap;
12
15
  font-family: var(--webchat-fontFamilyBase);
13
16
  font-size: var(--webchat-fontSizeBase300);
14
17
  font-weight: var(--webchat-fontWeightRegular);
18
+ gap: var(--webchat-spacingVerticalXS);
15
19
  grid-area: body;
16
20
  line-height: var(--webchat-lineHeightBase300);
17
21
  text-align: center;
18
22
  }
19
23
 
20
- :global(.webchat-fluent) .pre-chat-message-activity__body--placeholder {
24
+ :global(.webchat-fluent) .pre-chat-message-activity__body--blueprint {
21
25
  opacity: 60%;
26
+
27
+ .pre-chat-message-activity__body-avatar {
28
+ filter: grayscale(1);
29
+ }
22
30
  }
23
31
 
24
- :global(.webchat-fluent) .pre-chat-message-activity__body h2 {
32
+ :global(.webchat-fluent) .pre-chat-message-activity__body-avatar {
33
+ border-radius: var(--webchat-borderRadiusMedium);
34
+ height: 64px;
35
+ margin-block-end: var(--webchat-spacingVerticalM);
36
+ }
37
+
38
+ :global(.webchat-fluent) .pre-chat-message-activity__body-title {
25
39
  color: var(--webchat-colorNeutralForeground1);
26
40
  font-family: inherit;
27
- font-weight: var(--webchat-fontWeightSemibold);
28
41
  font-size: var(--webchat-fontSizeHero700);
42
+ font-weight: var(--webchat-fontWeightSemibold);
29
43
  line-height: var(--webchat-lineHeightHero700);
30
- margin: var(--webchat-spacingVerticalL) 0 0;
44
+ margin: 0;
31
45
  }
32
46
 
33
- :global(.webchat-fluent) .pre-chat-message-activity__body img {
34
- border-radius: 4px;
35
- height: 64px;
47
+ :global(.webchat-fluent) .pre-chat-message-activity__body-subtitle {
48
+ font-size: var(--webchat-fontSizeBase300);
49
+ line-height: var(--webchat-lineHeightBase300);
36
50
  }
37
51
 
38
52
  :global(.webchat-fluent) .pre-chat-message-activity__toolbar {
@@ -1,50 +1,56 @@
1
- /* eslint-disable react/no-danger */
2
1
  import { hooks } from 'botframework-webchat-component';
2
+ import { type WebChatActivity } from 'botframework-webchat-core';
3
3
  import cx from 'classnames';
4
- import { getOrgSchemaMessage, type WebChatActivity } from 'botframework-webchat-core';
5
- import React, { Fragment, memo, useMemo } from 'react';
4
+ import React, { memo, useMemo } from 'react';
6
5
  import { useStyles } from '../../styles/index.js';
7
6
  import styles from './PreChatMessageActivity.module.css';
8
7
  import StarterPromptsToolbar from './StarterPromptsToolbar.js';
9
- import StarterPromptsCardAction from './StarterPromptsCardAction.js';
8
+ import { useActivityAuthor } from '../activity/index.js';
10
9
 
11
10
  type Props = Readonly<{ activity: WebChatActivity & { type: 'message' } }>;
12
11
 
13
- const { useRenderMarkdownAsHTML } = hooks;
12
+ const { useLocalizer, useRenderMarkdownAsHTML, useUIState } = hooks;
14
13
 
15
14
  const PreChatMessageActivity = ({ activity }: Props) => {
15
+ const [uiState] = useUIState();
16
16
  const classNames = useStyles(styles);
17
17
  const renderMarkdownAsHTML = useRenderMarkdownAsHTML();
18
+ const localize = useLocalizer();
19
+
20
+ const author = useActivityAuthor(activity);
18
21
 
19
22
  const html = useMemo(
20
- () => (renderMarkdownAsHTML ? { __html: renderMarkdownAsHTML(activity.text || '') } : { __html: '' }),
21
- [activity.text, renderMarkdownAsHTML]
23
+ () => (renderMarkdownAsHTML ? { __html: renderMarkdownAsHTML(author?.description || '') } : { __html: '' }),
24
+ [author?.description, renderMarkdownAsHTML]
22
25
  );
23
26
 
24
- const entity = getOrgSchemaMessage(activity?.entities || []);
25
- const isPlaceHolder = entity?.keywords?.includes('PreChatMessage') && entity.creativeWorkStatus === 'Placeholder';
26
-
27
27
  return (
28
28
  <div className={classNames['pre-chat-message-activity']}>
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
- />
29
+ {author && (
30
+ <div
31
+ className={cx(
32
+ classNames['pre-chat-message-activity__body'],
33
+ uiState === 'blueprint' && classNames['pre-chat-message-activity__body--blueprint']
34
+ )}
35
+ >
36
+ {author.image && (
37
+ <img
38
+ alt={localize('AVATAR_ALT', author.name)}
39
+ className={classNames['pre-chat-message-activity__body-avatar']}
40
+ src={author.image}
41
+ />
42
+ )}
43
+ {author.name && <h2 className={classNames['pre-chat-message-activity__body-title']}>{author.name}</h2>}
44
+ {author.description && (
45
+ // eslint-disable-next-line react/no-danger
46
+ <div className={classNames['pre-chat-message-activity__body-subtitle']} dangerouslySetInnerHTML={html} />
47
+ )}
48
+ </div>
49
+ )}
36
50
  <StarterPromptsToolbar
37
51
  cardActions={activity.suggestedActions?.actions || []}
38
52
  className={classNames['pre-chat-message-activity__toolbar']}
39
- >
40
- {isPlaceHolder && (
41
- <Fragment>
42
- <StarterPromptsCardAction />
43
- <StarterPromptsCardAction />
44
- <StarterPromptsCardAction />
45
- </Fragment>
46
- )}
47
- </StarterPromptsToolbar>
53
+ />
48
54
  </div>
49
55
  );
50
56
  };
@@ -7,13 +7,14 @@
7
7
  color: var(--webchat-colorNeutralForeground1);
8
8
  cursor: pointer;
9
9
  display: grid;
10
- gap: 8px;
10
+ gap: var(--webchat-spacingVerticalS);
11
11
  grid-template-areas: 'title' 'subtitle';
12
12
  grid-template-columns: 1fr;
13
13
  grid-template-rows: auto 1fr;
14
14
  overflow: hidden;
15
15
  padding: 16px 20px;
16
16
  text-align: left;
17
+ transition: background-color var(--webchat-durationNormal) var(--webchat-curveDecelerateMid);
17
18
  user-select: none;
18
19
 
19
20
  &:has(.pre-chat-message-activity__card-action-image) {
@@ -21,19 +22,24 @@
21
22
  grid-template-columns: 20px 1fr;
22
23
  }
23
24
 
24
- &:disabled {
25
+ &:empty {
26
+ row-gap: 14px;
27
+ }
28
+
29
+ &[aria-disabled='true'],
30
+ &:empty {
25
31
  cursor: default;
26
32
  }
27
33
 
28
- &:disabled:not(:empty) {
34
+ &[aria-disabled='true']:not(:empty) {
29
35
  background-color: var(--webchat-colorNeutralBackgroundDisabled);
30
36
  }
31
37
 
32
- &:hover:not(:disabled) {
38
+ &:hover:not([aria-disabled='true'], :empty) {
33
39
  background-color: var(--webchat-colorNeutralBackground1Hover);
34
40
  }
35
41
 
36
- &:active:not(:disabled) {
42
+ &:active:not([aria-disabled='true'], :empty) {
37
43
  background-color: var(--webchat-colorNeutralBackground1Pressed);
38
44
  }
39
45
 
@@ -42,24 +48,57 @@
42
48
  outline-offset: -2px;
43
49
  }
44
50
 
51
+ &:empty::after,
45
52
  &:empty::before {
53
+ animation: blueprintAnimation 3s linear infinite;
54
+ background-attachment: fixed;
55
+ background-image: linear-gradient(
56
+ -90deg,
57
+ var(--webchat-colorNeutralStencil1) 0%,
58
+ var(--webchat-colorNeutralStencil2) 50%,
59
+ var(--webchat-colorNeutralStencil1) 100%
60
+ );
61
+ background-size: 300% 100%;
46
62
  content: '';
47
63
  display: block;
48
- width: 66%;
49
- height: 18px;
50
- background-color: var(--webchat-colorNeutralBackground6);
51
- border-radius: 18px;
52
- opacity: 1;
64
+ }
65
+
66
+ /* animation-* needs to position after animation shorthand. */
67
+ @media (prefers-reduced-motion: reduce) {
68
+ &:empty::after,
69
+ &:empty::before {
70
+ animation-delay: -1s;
71
+ animation-play-state: paused;
72
+ }
73
+ }
74
+
75
+ &:dir(rtl) {
76
+ &:empty::after,
77
+ &:empty::before {
78
+ animation-direction: reverse;
79
+ }
53
80
  }
54
81
 
55
82
  &:empty::after {
56
- content: '';
57
- display: block;
58
- width: 100%;
59
- height: 16px;
60
- background-color: var(--webchat-colorNeutralBackground6);
61
83
  border-radius: 16px;
62
- opacity: 0.8;
84
+ height: 16px;
85
+ width: 100%;
86
+ }
87
+
88
+ &:empty::before {
89
+ border-radius: 18px;
90
+ height: 18px;
91
+ width: 66%;
92
+ }
93
+ }
94
+
95
+ @keyframes blueprintAnimation {
96
+ from {
97
+ background-position-x: 0%;
98
+ }
99
+
100
+ to {
101
+ background-position-x: -300%;
63
102
  }
64
103
  }
65
104
 
@@ -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
  };