Skip to content

Commit 51b05bb

Browse files
authored
Migrate attachments to CSS modules (#5491)
* Migrate attachments to CSS modules * Fix useInjectStyles tests * Fix nits * Fix naming
1 parent 85d7da8 commit 51b05bb

29 files changed

+418
-435
lines changed

packages/component/src/Composer.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ import useTheme from './providers/Theme/useTheme';
5151
import createDefaultSendBoxMiddleware from './SendBox/createMiddleware';
5252
import createDefaultSendBoxToolbarMiddleware from './SendBoxToolbar/createMiddleware';
5353
import createStyleSet from './Styles/createStyleSet';
54+
import WebChatTheme from './Styles/WebChatTheme';
5455
import useCustomPropertiesClassName from './Styles/useCustomPropertiesClassName';
5556
import { type ContextOf } from './types/ContextOf';
5657
import { type FocusTranscriptInit } from './types/internal/FocusTranscriptInit';
@@ -318,7 +319,7 @@ ComposerCore.propTypes = {
318319

319320
type ComposerProps = APIComposerProps & ComposerCoreProps;
320321

321-
const Composer = ({
322+
const InternalComposer = ({
322323
activityMiddleware,
323324
activityStatusMiddleware,
324325
attachmentForScreenReaderMiddleware,
@@ -486,6 +487,12 @@ const Composer = ({
486487
);
487488
};
488489

490+
const Composer = (props: ComposerProps) => (
491+
<WebChatTheme>
492+
<InternalComposer {...props} />
493+
</WebChatTheme>
494+
);
495+
489496
Composer.defaultProps = {
490497
...APIComposer.defaultProps,
491498
...ComposerCore.defaultProps,
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
:global(.webchat) .component-icon {
2+
min-width: var(--webchat__component-icon--size, 1em);
3+
min-height: var(--webchat__component-icon--size, 1em);
4+
place-self: center;
5+
6+
/* Use the image as texture. */
7+
background-image: var(--webchat__component-icon--image, none);
8+
background-position: center;
9+
background-repeat: no-repeat;
10+
background-size: var(--webchat__component-icon--size, 1em);
11+
12+
/* If image is not set, fallback to solid color. */
13+
background-color: var(--webchat__component-icon--color, transparent);
14+
15+
/* 3. Set the mask if any. */
16+
-webkit-mask-image: var(--webchat__component-icon--mask);
17+
-webkit-mask-position: center;
18+
-webkit-mask-repeat: no-repeat;
19+
-webkit-mask-size: var(--webchat__component-icon--size, 1em);
20+
mask-image: var(--webchat__component-icon--mask);
21+
mask-position: center;
22+
mask-repeat: no-repeat;
23+
mask-size: var(--webchat__component-icon--size, 1em);
24+
}
25+
26+
/* #region: Appearance */
27+
:global(.webchat) .appearance--text {
28+
--webchat__component-icon--color: currentColor;
29+
}
30+
/* #endregion */
31+
32+
/* #region: Icons */
33+
:global(.webchat) .icon--dismiss {
34+
--webchat__component-icon--mask: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><path d="m4.09 4.22.06-.07a.5.5 0 0 1 .63-.06l.07.06L10 9.29l5.15-5.14a.5.5 0 0 1 .63-.06l.07.06c.18.17.2.44.06.63l-.06.07L10.71 10l5.14 5.15c.18.17.2.44.06.63l-.06.07a.5.5 0 0 1-.63.06l-.07-.06L10 10.71l-5.15 5.14a.5.5 0 0 1-.63.06l-.07-.06a.5.5 0 0 1-.06-.63l.06-.07L9.29 10 4.15 4.85a.5.5 0 0 1-.06-.63l.06-.07-.06.07Z"/></svg>');
35+
}
36+
/* #endregion */
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { validateProps } from 'botframework-webchat-react-valibot';
2+
import { useStyles } from 'botframework-webchat-styles/react';
3+
import cx from 'classnames';
4+
import React, { memo } from 'react';
5+
import { object, optional, pipe, readonly, string, type InferInput } from 'valibot';
6+
7+
import createIconComponent from '../Utils/createIconComponent';
8+
import styles from './ComponentIcon.module.css';
9+
10+
const componentIconPropsSchema = pipe(
11+
object({
12+
appearance: optional(string()),
13+
className: optional(string()),
14+
icon: optional(string()),
15+
size: optional(string())
16+
}),
17+
readonly()
18+
);
19+
20+
type ComponentIconProps = InferInput<typeof componentIconPropsSchema>;
21+
22+
function BaseComponentIcon(props: ComponentIconProps) {
23+
const { className } = validateProps(componentIconPropsSchema, props);
24+
25+
const classNames = useStyles(styles);
26+
27+
return <div className={cx(classNames['component-icon'], className)} />;
28+
}
29+
30+
const ComponentIcon = createIconComponent(styles, BaseComponentIcon);
31+
32+
ComponentIcon.displayName = 'ComponentIcon';
33+
34+
export default memo(ComponentIcon);
35+
export { componentIconPropsSchema, type ComponentIconProps };
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default as ComponentIcon } from './ComponentIcon';

packages/component/src/ModdableIcon/ModdableIcon.tsx

Lines changed: 0 additions & 41 deletions
This file was deleted.

packages/component/src/ModdableIcon/ModdableIconStyle.ts

Lines changed: 0 additions & 25 deletions
This file was deleted.
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
:global(.webchat) .send-box-attachment-bar {
2+
grid-area: attachment-bar;
3+
4+
&.send-box-attachment-bar--as-list-item {
5+
max-height: var(--webchat__max-height--send-box-attachment-bar);
6+
overflow-y: auto;
7+
scrollbar-gutter: stable;
8+
scrollbar-width: thin;
9+
}
10+
11+
&.send-box-attachment-bar--as-thumbnail {
12+
overflow-x: auto;
13+
}
14+
15+
.send-box-attachment-bar__box {
16+
gap: 4px;
17+
}
18+
19+
&.send-box-attachment-bar--as-list-item .send-box-attachment-bar__box {
20+
display: grid;
21+
grid-template-columns: 1fr 1fr;
22+
23+
&:not(:empty) {
24+
padding: 4px;
25+
}
26+
}
27+
28+
&.send-box-attachment-bar--as-thumbnail .send-box-attachment-bar__box {
29+
display: flex;
30+
scrollbar-width: thin;
31+
}
32+
}

packages/component/src/SendBox/AttachmentBar/AttachmentBar.tsx

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import { hooks } from 'botframework-webchat-api';
22
import { validateProps } from 'botframework-webchat-react-valibot';
3-
import classNames from 'classnames';
3+
import cx from 'classnames';
44
import React, { memo, useCallback, useMemo } from 'react';
55
import { useRefFrom } from 'use-ref-from';
6-
import { type InferInput, object, optional, pipe, readonly, string } from 'valibot';
6+
import { useStyles } from 'botframework-webchat-styles/react';
7+
import { object, optional, pipe, readonly, string, type InferInput } from 'valibot';
78

8-
import { useStyleSet } from '../../hooks';
9+
import styles from './AttachmentBar.module.css';
910
import testIds from '../../testIds';
1011
import AttachmentBarItem from './AttachmentBarItem';
1112

@@ -24,7 +25,7 @@ function SendBoxAttachmentBar(props: SendBoxAttachmentBarProps) {
2425
const { className } = validateProps(sendBoxAttachmentBarPropsSchema, props);
2526

2627
const [sendBoxAttachments, setSendBoxAttachments] = useSendBoxAttachments();
27-
const [{ sendBoxAttachmentBar: sendBoxAttachmentBarClassName }] = useStyleSet();
28+
const classNames = useStyles(styles);
2829
const [{ sendBoxAttachmentBarMaxThumbnail }] = useStyleOptions();
2930

3031
const mode = useMemo(
@@ -45,18 +46,17 @@ function SendBoxAttachmentBar(props: SendBoxAttachmentBarProps) {
4546
return (
4647
sendBoxAttachments.length > 0 && (
4748
<div
48-
className={classNames(
49-
sendBoxAttachmentBarClassName,
50-
'webchat__send-box-attachment-bar',
49+
className={cx(
50+
classNames['send-box-attachment-bar'],
5151
{
52-
'webchat__send-box-attachment-bar--as-list-item': mode === 'list item',
53-
'webchat__send-box-attachment-bar--as-thumbnail': mode === 'thumbnail'
52+
[classNames['send-box-attachment-bar--as-list-item']]: mode === 'list item',
53+
[classNames['send-box-attachment-bar--as-thumbnail']]: mode === 'thumbnail'
5454
},
5555
className
5656
)}
5757
data-testid={testIds.sendBoxAttachmentBar}
5858
>
59-
<div className="webchat__send-box-attachment-bar__box">
59+
<div className={classNames['send-box-attachment-bar__box']}>
6060
{sendBoxAttachments.map((attachment, index) => (
6161
// eslint-disable-next-line react/no-array-index-key
6262
<AttachmentBarItem attachment={attachment} key={index} mode={mode} onDelete={handleAttachmentDelete} />
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
/* #region List item */
2+
:global(.webchat) .send-box-attachment-bar-item {
3+
display: grid;
4+
flex-shrink: 0;
5+
grid-template-rows: auto;
6+
font-family: var(--webchat__font--primary);
7+
8+
&.send-box-attachment-bar-item--as-list-item {
9+
border-radius: 4px;
10+
box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.14), 0px 0px 2px rgba(0, 0, 0, 0.12);
11+
grid-template-areas: 'body auxiliary';
12+
grid-template-columns: 1fr auto;
13+
padding: 2px;
14+
}
15+
16+
&.send-box-attachment-bar-item--as-thumbnail {
17+
aspect-ratio: 1 / 1;
18+
border: solid 1px rgba(0, 0, 0, 0.25); /* Figma has border-width of 0.96px. */
19+
border-radius: 8px; /* Figma is 7.68px. */
20+
grid-template-areas: 'body';
21+
grid-template-columns: auto;
22+
height: 80px; /* <= 87px would fit white-label design with 3 thumbnails. */
23+
overflow: hidden;
24+
}
25+
}
26+
/* #endregion */
27+
28+
/* #region Delete button */
29+
:global(.webchat) .send-box-attachment-bar-item__delete-button {
30+
/* https://react.fluentui.dev/?path=/docs/theme-colors--docs */
31+
appearance: none;
32+
align-items: center;
33+
background-color: White; /* Background/colorNeutralBackground1 */
34+
border-color: #d1d1d1; /* Stroke/colorNeutralStroke1 */
35+
border-radius: 4px; /* BorderRadiusXS is not defined in Fluent UI, guessing it is 4px. */
36+
cursor: pointer;
37+
grid-area: body;
38+
justify-self: end;
39+
opacity: 1;
40+
padding: 0;
41+
transition: opacity 50ms; /* Assume ultra-fast. */
42+
color: #242424; /* Background/colorNeutralForeground1 */
43+
--webchat__component-icon--size: 19px;
44+
45+
&:hover {
46+
background-color: #f5f5f5; /* Background/colorNeutralBackground1Hover */
47+
border-color: #c7c7c7; /* Stroke/colorNeutralStroke1Hover */
48+
}
49+
50+
&:active {
51+
background-color: #e0e0e0; /* Background/colorNeutralBackground1Pressed */
52+
border-color: #c7c7c7; /* Stroke/colorNeutralStroke1Pressed */
53+
}
54+
55+
&:disabled,
56+
&[aria-disabled='true'] {
57+
background-color: #f0f0f0; /* Background/colorNeutralBackgroundDisabled */
58+
border-color: #e0e0e0; /* Stroke/colorNeutralStrokeDisabled */
59+
color: #bdbdbd; /* Stroke/colorNeutralForegroundDisabled */
60+
}
61+
}
62+
63+
@media (prefers-color-scheme: dark) {
64+
:global(.webchat.theme--prefers-color-scheme) .send-box-attachment-bar-item__delete-button {
65+
background-color: #292929; /* Background/colorNeutralBackground1 */
66+
border-color: #666666; /* Stroke/colorNeutralStroke1 */
67+
color: #ffffff; /* Background/colorNeutralBackground1 */
68+
69+
&:hover {
70+
background-color: #3d3d3d; /* Background/colorNeutralBackground1Hover */
71+
border-color: #757575; /* Stroke/colorNeutralStroke1Hover */
72+
}
73+
74+
&:active {
75+
background-color: #1f1f1f; /* Background/colorNeutralBackground1Pressed */
76+
border-color: #6b6b6b; /* Stroke/colorNeutralStroke1Pressed */
77+
}
78+
79+
&:disabled,
80+
&[aria-disabled='true'] {
81+
background-color: #141414; /* Background/colorNeutralBackgroundDisabled */
82+
border-color: #424242; /* Stroke/colorNeutralStrokeDisabled */
83+
color: #5c5c5c; /* Stroke/colorNeutralForegroundDisabled */
84+
}
85+
}
86+
}
87+
88+
:global(.webchat)
89+
.send-box-attachment-bar-item.send-box-attachment-bar-item--as-list-item
90+
.send-box-attachment-bar-item__delete-button {
91+
border: 0;
92+
grid-area: auxiliary;
93+
height: 24px;
94+
width: 24px;
95+
96+
.send-box-attachment-bar-item__dismiss-icon {
97+
--webchat__component-icon--size: 7px;
98+
--webchat__component-icon--mask: url('data:image/svg+xml;utf8,<svg viewBox="0 0 8 8" xmlns="http://www.w3.org/2000/svg"><path d="M0.0885911 0.215694L0.146447 0.146447C0.320013 -0.0271197 0.589437 -0.046405 0.784306 0.0885911L0.853553 0.146447L4 3.293L7.14645 0.146447C7.34171 -0.0488154 7.65829 -0.0488154 7.85355 0.146447C8.04882 0.341709 8.04882 0.658291 7.85355 0.853553L4.707 4L7.85355 7.14645C8.02712 7.32001 8.0464 7.58944 7.91141 7.78431L7.85355 7.85355C7.67999 8.02712 7.41056 8.0464 7.21569 7.91141L7.14645 7.85355L4 4.707L0.853553 7.85355C0.658291 8.04882 0.341709 8.04882 0.146447 7.85355C-0.0488154 7.65829 -0.0488154 7.34171 0.146447 7.14645L3.293 4L0.146447 0.853553C-0.0271197 0.679987 -0.046405 0.410563 0.0885911 0.215694L0.146447 0.146447L0.0885911 0.215694Z"/></svg>');
99+
}
100+
}
101+
102+
:global(.webchat)
103+
.send-box-attachment-bar-item.send-box-attachment-bar-item--as-thumbnail
104+
.send-box-attachment-bar-item__delete-button {
105+
border-style: solid; /* Border color will be set elsewhere. */
106+
border-width: 1px; /* Figma has border-width of 0.96px. */
107+
grid-area: body; /* This was already set, but keeping for explicitness from original */
108+
height: 23px; /* Figma is 23.04px. */
109+
margin: 8px; /* Figma is 7.68px. */
110+
width: 23px; /* Figma is 23.04px. */
111+
}
112+
113+
@media not (prefers-reduced-motion: reduce) {
114+
:global(.webchat)
115+
.send-box-attachment-bar-item.send-box-attachment-bar-item--as-thumbnail:not(:hover):not(:focus-within)
116+
.send-box-attachment-bar-item__delete-button {
117+
opacity: 0;
118+
}
119+
}
120+
/* #endregion */
121+
122+
/* #region Preview */
123+
:global(.webchat) .send-box-attachment-bar-item__preview {
124+
align-items: center;
125+
display: grid;
126+
grid-area: body;
127+
overflow: hidden;
128+
}
129+
130+
:global(.webchat)
131+
.send-box-attachment-bar-item.send-box-attachment-bar-item--as-list-item
132+
.send-box-attachment-bar-item__preview {
133+
padding-inline: 8px;
134+
}
135+
/* #endregion */

0 commit comments

Comments
 (0)