Skip to content

Commit 6bcf06a

Browse files
authored
feat: add BaseImage component with image fallback display (#2193)
1 parent 3c2da43 commit 6bcf06a

37 files changed

+1021
-59
lines changed
374 KB
Loading
23.5 KB
Loading
27.2 KB
Loading

docusaurus/docs/React/components/contexts/component-context.mdx

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,22 @@ Custom UI component to display a user's avatar.
6969
| --------- | ---------------------------------------------------------- |
7070
| component | <GHComponentLink text='Avatar' path='/Avatar/Avatar.tsx'/> |
7171

72+
### BaseImage
73+
74+
Custom UI component to display image resp. a fallback in case of load error, in `<img/>` element. The default resp. custom (from `ComponentContext`) `BaseImage` component is rendered by:
75+
76+
- <GHComponentLink text='Image' path='/Gallery/Image.tsx'/> - single image attachment in message list
77+
- <GHComponentLink text='Gallery' path='/Gallery/Gallery.tsx'/> - group of image attachments in message list
78+
- <GHComponentLink text='AttachmentPreviewList' path='/MessageInput/AttachmentPreviewList.tsx'/> - image uploads preview in message input (composer)
79+
80+
The `BaseImage` component accepts the same props as `<img/>` element.
81+
82+
The [default `BaseImage` component](../../utility-components/base-image) tries to load and display an image and if the load fails, then an SVG image fallback is applied to the `<img/>` element as a CSS mask targeting attached `str-chat__base-image--load-failed` class.
83+
84+
| Type | Default |
85+
|-----------|-----------------------------------------------------------------------|
86+
| component | <GHComponentLink text='BaseImage' path='/Gallery/BaseImage.tsx'/> |
87+
7288
### CooldownTimer
7389

7490
Custom UI component to display the slow mode cooldown timer.
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
---
2+
id: base-image
3+
sidebar_position: 9
4+
title: BaseImage
5+
---
6+
7+
import ImageShowcase from '@site/src/components/ImageShowcase';
8+
import BaseImageInGallery from '../../assets/base-image-fallback-in-attachment-gallery.png'
9+
import BaseImageInAttachmentPreview from '../../assets/base-image-fallback-in-attachment-preview.png'
10+
import BaseImageInImageAttachment from '../../assets/base-image-fallback-in-image-attachment.png'
11+
12+
The `BaseImage` component's purpose is to display an image or a fallback if loading the resource has failed. The component is used internally by:
13+
14+
- `Image` component - used to display image attachments in `Message`
15+
- `Gallery` component - used to display image gallery among `Message` attachments
16+
- `AttachmentPreviewList` component - used to display attachment previews in `MessageInput`
17+
18+
The default image fallbacks are rendered as follows:
19+
20+
<ImageShowcase
21+
border
22+
items={[
23+
{
24+
image: BaseImageInImageAttachment,
25+
caption: <span>BaseImage in image attachment</span>,
26+
alt: 'BaseImage in image attachment',
27+
},
28+
{
29+
image: BaseImageInGallery,
30+
caption: <span>BaseImage in image attachment gallery</span>,
31+
alt: 'BaseImage in image attachment gallery',
32+
},
33+
{
34+
image: BaseImageInAttachmentPreview,
35+
caption: <span>BaseImage in attachment preview</span>,
36+
alt: 'BaseImage in attachment preview',
37+
},
38+
]}
39+
/>
40+
41+
## Usage
42+
43+
### Custom image fallback
44+
45+
The default image fallback can be changed by applying a new CSS data image to the fallback mask in the `BaseImage`'s `<img/>` element. The data image has to be assigned to a CSS variable `--str-chat__image-fallback-icon` within the scope of `.str-chat` class. An example follows:
46+
47+
```css
48+
49+
.str-chat {
50+
--str-chat__image-fallback-icon: url("data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAiIGhlaWdodD0iOSIgdmlld0JveD0iMCAwIDEwIDkiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgICA8cGF0aCBkPSJNOS4xOTk0OSAwLjMwNTY3MUM4LjkzOTQ5IDAuMDQ1NjcwNyA4LjUxOTQ5IDAuMDQ1NjcwNyA4LjI1OTQ5IDAuMzA1NjcxTDQuOTk5NDkgMy41NTlMMS43Mzk0OSAwLjI5OTAwNEMxLjQ3OTQ5IDAuMDM5MDAzOSAxLjA1OTQ5IDAuMDM5MDAzOSAwLjc5OTQ5MiAwLjI5OTAwNEMwLjUzOTQ5MiAwLjU1OTAwNCAwLjUzOTQ5MiAwLjk3OTAwNCAwLjc5OTQ5MiAxLjIzOUw0LjA1OTQ5IDQuNDk5TDAuNzk5NDkyIDcuNzU5QzAuNTM5NDkyIDguMDE5IDAuNTM5NDkyIDguNDM5IDAuNzk5NDkyIDguNjk5QzEuMDU5NDkgOC45NTkgMS40Nzk0OSA4Ljk1OSAxLjczOTQ5IDguNjk5TDQuOTk5NDkgNS40MzlMOC4yNTk0OSA4LjY5OUM4LjUxOTQ5IDguOTU5IDguOTM5NDkgOC45NTkgOS4xOTk0OSA4LjY5OUM5LjQ1OTQ5IDguNDM5IDkuNDU5NDkgOC4wMTkgOS4xOTk0OSA3Ljc1OUw1LjkzOTQ5IDQuNDk5TDkuMTk5NDkgMS4yMzlDOS40NTI4MyAwLjk4NTY3MSA5LjQ1MjgzIDAuNTU5MDA0IDkuMTk5NDkgMC4zMDU2NzFaIiBmaWxsPSIjNzI3NjdFIi8+Cjwvc3ZnPgo=");
51+
}
52+
```
53+
54+
We can change the mask dimensions or color by applying the following rules to the image's class `.str-chat__base-image--load-failed`, that signals the image load has failed:
55+
56+
```css
57+
:root{
58+
--custom-icon-fill-color: #223344;
59+
--custom-icon-width-and-height: 4rem 4rem;
60+
}
61+
62+
.str-chat__base-image--load-failed {
63+
mask-size: var(--custom-icon-width-and-height);
64+
-webkit-mask-size: var(--custom-icon-width-and-height);
65+
background-color: var(--custom-icon-fill-color);
66+
}
67+
```
68+
69+
### Custom BaseImage
70+
71+
The default `BaseImage` can be overridden by passing a custom component to `Channel` props:
72+
73+
74+
```tsx
75+
import {ComponentProps } from 'react';
76+
import { Channel } from 'stream-chat-react';
77+
78+
const CustomBaseImage = (props: ComponentProps<'img'>) => {
79+
// your implementation...
80+
}
81+
82+
export const MyUI = () => {
83+
return (
84+
<Channel BaseImage={CustomBaseImage}>
85+
{{/* more components */ }}
86+
</Channel>
87+
);
88+
};
89+
```
90+
91+
## Props
92+
93+
The component accepts the `img` component props.
94+

src/components/Attachment/Audio.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ export const ProgressBar = ({ onClick, progress }: ProgressBarProps) => (
107107
role='progressbar'
108108
style={{
109109
background: `linear-gradient(
110-
to right,
110+
to right,
111111
var(--str-chat__primary-color),
112112
var(--str-chat__primary-color) ${progress}%,
113113
var(--str-chat__disabled-color) ${progress}%,

src/components/Attachment/__tests__/Card.test.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -164,10 +164,11 @@ describe('Card', () => {
164164
>
165165
<img
166166
alt="test"
167-
class="str-chat__message-attachment--img"
167+
class="str-chat__message-attachment--img str-chat__base-image"
168168
data-testid="image-test"
169169
src="test.jpg"
170170
tabindex="0"
171+
title="test"
171172
/>
172173
</div>
173174
<div
@@ -217,10 +218,11 @@ describe('Card', () => {
217218
>
218219
<img
219220
alt="test"
220-
class="str-chat__message-attachment--img"
221+
class="str-chat__message-attachment--img str-chat__base-image"
221222
data-testid="image-test"
222223
src="test.jpg"
223224
tabindex="0"
225+
title="test"
224226
/>
225227
</div>
226228
<div
@@ -296,10 +298,11 @@ describe('Card', () => {
296298
>
297299
<img
298300
alt="test"
299-
class="str-chat__message-attachment--img"
301+
class="str-chat__message-attachment--img str-chat__base-image"
300302
data-testid="image-test"
301303
src="test.jpg"
302304
tabindex="0"
305+
title="test"
303306
/>
304307
</div>
305308
</div>

src/components/Attachment/__tests__/__snapshots__/Card.test.js.snap

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,11 @@ exports[`Card theme V2 (1) should render card without caption if attachment type
1111
>
1212
<img
1313
alt="dummyAttachment_title"
14-
class="str-chat__message-attachment--img"
14+
class="str-chat__message-attachment--img str-chat__base-image"
1515
data-testid="image-test"
1616
src="dummyAttachment_image_url"
1717
tabindex="0"
18+
title="dummyAttachment_title"
1819
/>
1920
</div>
2021
<div
@@ -139,10 +140,11 @@ exports[`Card theme V2 (3) should render card without caption if attachment type
139140
>
140141
<img
141142
alt="dummyAttachment_title"
142-
class="str-chat__message-attachment--img"
143+
class="str-chat__message-attachment--img str-chat__base-image"
143144
data-testid="image-test"
144145
src="dummyAttachment_image_url"
145146
tabindex="0"
147+
title="dummyAttachment_title"
146148
/>
147149
</div>
148150
<div
@@ -232,10 +234,11 @@ exports[`Card theme V2 (7) should render audio with caption using og_scrape_url
232234
>
233235
<img
234236
alt="dummyAttachment_title"
235-
class="str-chat__message-attachment--img"
237+
class="str-chat__message-attachment--img str-chat__base-image"
236238
data-testid="image-test"
237239
src="dummyAttachment_image_url"
238240
tabindex="0"
241+
title="dummyAttachment_title"
239242
/>
240243
</div>
241244
<div
@@ -388,10 +391,11 @@ exports[`Card theme V2 (9) should render image with caption using og_scrape_url
388391
>
389392
<img
390393
alt="dummyAttachment_title"
391-
class="str-chat__message-attachment--img"
394+
class="str-chat__message-attachment--img str-chat__base-image"
392395
data-testid="image-test"
393396
src="dummyAttachment_image_url"
394397
tabindex="0"
398+
title="dummyAttachment_title"
395399
/>
396400
</div>
397401
<div
@@ -441,10 +445,11 @@ exports[`Card theme V2 (10) should render audio without title if attachment type
441445
>
442446
<img
443447
alt="dummyAttachment_thumb_url"
444-
class="str-chat__message-attachment--img"
448+
class="str-chat__message-attachment--img str-chat__base-image"
445449
data-testid="image-test"
446450
src="dummyAttachment_image_url"
447451
tabindex="0"
452+
title="dummyAttachment_thumb_url"
448453
/>
449454
</div>
450455
<div
@@ -587,10 +592,11 @@ exports[`Card theme V2 (12) should render image without title if attachment type
587592
>
588593
<img
589594
alt="dummyAttachment_thumb_url"
590-
class="str-chat__message-attachment--img"
595+
class="str-chat__message-attachment--img str-chat__base-image"
591596
data-testid="image-test"
592597
src="dummyAttachment_image_url"
593598
tabindex="0"
599+
title="dummyAttachment_thumb_url"
594600
/>
595601
</div>
596602
<div
@@ -635,10 +641,11 @@ exports[`Card theme V2 (13) should render audio without title and with caption u
635641
>
636642
<img
637643
alt="dummyAttachment_thumb_url"
638-
class="str-chat__message-attachment--img"
644+
class="str-chat__message-attachment--img str-chat__base-image"
639645
data-testid="image-test"
640646
src="dummyAttachment_image_url"
641647
tabindex="0"
648+
title="dummyAttachment_thumb_url"
642649
/>
643650
</div>
644651
<div
@@ -781,10 +788,11 @@ exports[`Card theme V2 (15) should render image without title and with caption u
781788
>
782789
<img
783790
alt="dummyAttachment_thumb_url"
784-
class="str-chat__message-attachment--img"
791+
class="str-chat__message-attachment--img str-chat__base-image"
785792
data-testid="image-test"
786793
src="dummyAttachment_image_url"
787794
tabindex="0"
795+
title="dummyAttachment_thumb_url"
788796
/>
789797
</div>
790798
<div
@@ -1014,10 +1022,11 @@ exports[`Card theme V2 (19) should render image loaded from thumb_url not audio
10141022
>
10151023
<img
10161024
alt="dummyAttachment_title"
1017-
class="str-chat__message-attachment--img"
1025+
class="str-chat__message-attachment--img str-chat__base-image"
10181026
data-testid="image-test"
10191027
src="dummyAttachment_thumb_url"
10201028
tabindex="0"
1029+
title="dummyAttachment_title"
10211030
/>
10221031
</div>
10231032
<div
@@ -1072,10 +1081,11 @@ exports[`Card theme V2 (20) should render image loaded from thumb_url not video
10721081
>
10731082
<img
10741083
alt="dummyAttachment_title"
1075-
class="str-chat__message-attachment--img"
1084+
class="str-chat__message-attachment--img str-chat__base-image"
10761085
data-testid="image-test"
10771086
src="dummyAttachment_thumb_url"
10781087
tabindex="0"
1088+
title="dummyAttachment_title"
10791089
/>
10801090
</div>
10811091
<div
@@ -1125,10 +1135,11 @@ exports[`Card theme V2 (21) should render image loaded from thumb_url if attachm
11251135
>
11261136
<img
11271137
alt="dummyAttachment_title"
1128-
class="str-chat__message-attachment--img"
1138+
class="str-chat__message-attachment--img str-chat__base-image"
11291139
data-testid="image-test"
11301140
src="dummyAttachment_thumb_url"
11311141
tabindex="0"
1142+
title="dummyAttachment_title"
11321143
/>
11331144
</div>
11341145
<div
@@ -1178,10 +1189,11 @@ exports[`Card theme V2 (22) should render image loaded from image_url not audio
11781189
>
11791190
<img
11801191
alt="dummyAttachment_title"
1181-
class="str-chat__message-attachment--img"
1192+
class="str-chat__message-attachment--img str-chat__base-image"
11821193
data-testid="image-test"
11831194
src="dummyAttachment_image_url"
11841195
tabindex="0"
1196+
title="dummyAttachment_title"
11851197
/>
11861198
</div>
11871199
<div
@@ -1236,10 +1248,11 @@ exports[`Card theme V2 (23) should render image loaded from image_url not video
12361248
>
12371249
<img
12381250
alt="dummyAttachment_title"
1239-
class="str-chat__message-attachment--img"
1251+
class="str-chat__message-attachment--img str-chat__base-image"
12401252
data-testid="image-test"
12411253
src="dummyAttachment_image_url"
12421254
tabindex="0"
1255+
title="dummyAttachment_title"
12431256
/>
12441257
</div>
12451258
<div
@@ -1289,10 +1302,11 @@ exports[`Card theme V2 (24) should render image loaded from image_url if attachm
12891302
>
12901303
<img
12911304
alt="dummyAttachment_title"
1292-
class="str-chat__message-attachment--img"
1305+
class="str-chat__message-attachment--img str-chat__base-image"
12931306
data-testid="image-test"
12941307
src="dummyAttachment_image_url"
12951308
tabindex="0"
1309+
title="dummyAttachment_title"
12961310
/>
12971311
</div>
12981312
<div
@@ -1342,10 +1356,11 @@ exports[`Card theme V2 (25) should render audio widget with image loaded from th
13421356
>
13431357
<img
13441358
alt="dummyAttachment_title"
1345-
class="str-chat__message-attachment--img"
1359+
class="str-chat__message-attachment--img str-chat__base-image"
13461360
data-testid="image-test"
13471361
src="dummyAttachment_image_url"
13481362
tabindex="0"
1363+
title="dummyAttachment_title"
13491364
/>
13501365
</div>
13511366
<div

src/components/Channel/Channel.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,9 @@ type ChannelPropsForwardedToComponentContext<
109109
AutocompleteSuggestionList?: ComponentContextValue<StreamChatGenerics>['AutocompleteSuggestionList'];
110110
/** UI component to display a user's avatar, defaults to and accepts same props as: [Avatar](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Avatar/Avatar.tsx) */
111111
Avatar?: ComponentContextValue<StreamChatGenerics>['Avatar'];
112-
/** Custom UI component to display the slow mode cooldown timer, defaults to and accepts same props as: [CooldownTimer](https://github.com/GetStream/stream-chat-react/blob/master/src/components/MessageInput/hooks/useCooldownTimer.tsx) */
112+
/** Custom UI component to display <img/> elements resp. a fallback in case of load error, defaults to and accepts same props as: [BaseImage](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Gallery/BaseImage.tsx) */
113+
BaseImage?: ComponentContextValue<StreamChatGenerics>['BaseImage'];
114+
/** Custom UI component to display the slow mode cooldown timer, defaults to and accepts same props as: [CooldownTimer](https://github.com/GetStream/stream-chat-react/blob/master/src/components/MessageInput/CooldownTimer.tsx) */
113115
CooldownTimer?: ComponentContextValue<StreamChatGenerics>['CooldownTimer'];
114116
/** Custom UI component for date separators, defaults to and accepts same props as: [DateSeparator](https://github.com/GetStream/stream-chat-react/blob/master/src/components/DateSeparator.tsx) */
115117
DateSeparator?: ComponentContextValue<StreamChatGenerics>['DateSeparator'];
@@ -1013,6 +1015,7 @@ const ChannelInner = <
10131015
AutocompleteSuggestionItem: props.AutocompleteSuggestionItem,
10141016
AutocompleteSuggestionList: props.AutocompleteSuggestionList,
10151017
Avatar: props.Avatar,
1018+
BaseImage: props.BaseImage,
10161019
CooldownTimer: props.CooldownTimer,
10171020
DateSeparator: props.DateSeparator,
10181021
EditMessageInput: props.EditMessageInput,

0 commit comments

Comments
 (0)