Skip to content

Commit

Permalink
feat: add BaseImage component with image fallback display (#2193)
Browse files Browse the repository at this point in the history
  • Loading branch information
MartinCupela authored Dec 1, 2023
1 parent 3c2da43 commit 6bcf06a
Show file tree
Hide file tree
Showing 37 changed files with 1,021 additions and 59 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 16 additions & 0 deletions docusaurus/docs/React/components/contexts/component-context.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,22 @@ Custom UI component to display a user's avatar.
| --------- | ---------------------------------------------------------- |
| component | <GHComponentLink text='Avatar' path='/Avatar/Avatar.tsx'/> |

### BaseImage

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:

- <GHComponentLink text='Image' path='/Gallery/Image.tsx'/> - single image attachment in message list
- <GHComponentLink text='Gallery' path='/Gallery/Gallery.tsx'/> - group of image attachments in message list
- <GHComponentLink text='AttachmentPreviewList' path='/MessageInput/AttachmentPreviewList.tsx'/> - image uploads preview in message input (composer)

The `BaseImage` component accepts the same props as `<img/>` element.

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.

| Type | Default |
|-----------|-----------------------------------------------------------------------|
| component | <GHComponentLink text='BaseImage' path='/Gallery/BaseImage.tsx'/> |

### CooldownTimer

Custom UI component to display the slow mode cooldown timer.
Expand Down
94 changes: 94 additions & 0 deletions docusaurus/docs/React/components/utility-components/base-image.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
---
id: base-image
sidebar_position: 9
title: BaseImage
---

import ImageShowcase from '@site/src/components/ImageShowcase';
import BaseImageInGallery from '../../assets/base-image-fallback-in-attachment-gallery.png'
import BaseImageInAttachmentPreview from '../../assets/base-image-fallback-in-attachment-preview.png'
import BaseImageInImageAttachment from '../../assets/base-image-fallback-in-image-attachment.png'

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:

- `Image` component - used to display image attachments in `Message`
- `Gallery` component - used to display image gallery among `Message` attachments
- `AttachmentPreviewList` component - used to display attachment previews in `MessageInput`

The default image fallbacks are rendered as follows:

<ImageShowcase
border
items={[
{
image: BaseImageInImageAttachment,
caption: <span>BaseImage in image attachment</span>,
alt: 'BaseImage in image attachment',
},
{
image: BaseImageInGallery,
caption: <span>BaseImage in image attachment gallery</span>,
alt: 'BaseImage in image attachment gallery',
},
{
image: BaseImageInAttachmentPreview,
caption: <span>BaseImage in attachment preview</span>,
alt: 'BaseImage in attachment preview',
},
]}
/>

## Usage

### Custom image fallback

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:

```css

.str-chat {
--str-chat__image-fallback-icon: url("data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAiIGhlaWdodD0iOSIgdmlld0JveD0iMCAwIDEwIDkiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgICA8cGF0aCBkPSJNOS4xOTk0OSAwLjMwNTY3MUM4LjkzOTQ5IDAuMDQ1NjcwNyA4LjUxOTQ5IDAuMDQ1NjcwNyA4LjI1OTQ5IDAuMzA1NjcxTDQuOTk5NDkgMy41NTlMMS43Mzk0OSAwLjI5OTAwNEMxLjQ3OTQ5IDAuMDM5MDAzOSAxLjA1OTQ5IDAuMDM5MDAzOSAwLjc5OTQ5MiAwLjI5OTAwNEMwLjUzOTQ5MiAwLjU1OTAwNCAwLjUzOTQ5MiAwLjk3OTAwNCAwLjc5OTQ5MiAxLjIzOUw0LjA1OTQ5IDQuNDk5TDAuNzk5NDkyIDcuNzU5QzAuNTM5NDkyIDguMDE5IDAuNTM5NDkyIDguNDM5IDAuNzk5NDkyIDguNjk5QzEuMDU5NDkgOC45NTkgMS40Nzk0OSA4Ljk1OSAxLjczOTQ5IDguNjk5TDQuOTk5NDkgNS40MzlMOC4yNTk0OSA4LjY5OUM4LjUxOTQ5IDguOTU5IDguOTM5NDkgOC45NTkgOS4xOTk0OSA4LjY5OUM5LjQ1OTQ5IDguNDM5IDkuNDU5NDkgOC4wMTkgOS4xOTk0OSA3Ljc1OUw1LjkzOTQ5IDQuNDk5TDkuMTk5NDkgMS4yMzlDOS40NTI4MyAwLjk4NTY3MSA5LjQ1MjgzIDAuNTU5MDA0IDkuMTk5NDkgMC4zMDU2NzFaIiBmaWxsPSIjNzI3NjdFIi8+Cjwvc3ZnPgo=");
}
```

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:

```css
:root{
--custom-icon-fill-color: #223344;
--custom-icon-width-and-height: 4rem 4rem;
}

.str-chat__base-image--load-failed {
mask-size: var(--custom-icon-width-and-height);
-webkit-mask-size: var(--custom-icon-width-and-height);
background-color: var(--custom-icon-fill-color);
}
```

### Custom BaseImage

The default `BaseImage` can be overridden by passing a custom component to `Channel` props:


```tsx
import {ComponentProps } from 'react';
import { Channel } from 'stream-chat-react';

const CustomBaseImage = (props: ComponentProps<'img'>) => {
// your implementation...
}

export const MyUI = () => {
return (
<Channel BaseImage={CustomBaseImage}>
{{/* more components */ }}
</Channel>
);
};
```

## Props

The component accepts the `img` component props.

2 changes: 1 addition & 1 deletion src/components/Attachment/Audio.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ export const ProgressBar = ({ onClick, progress }: ProgressBarProps) => (
role='progressbar'
style={{
background: `linear-gradient(
to right,
to right,
var(--str-chat__primary-color),
var(--str-chat__primary-color) ${progress}%,
var(--str-chat__disabled-color) ${progress}%,
Expand Down
9 changes: 6 additions & 3 deletions src/components/Attachment/__tests__/Card.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -164,10 +164,11 @@ describe('Card', () => {
>
<img
alt="test"
class="str-chat__message-attachment--img"
class="str-chat__message-attachment--img str-chat__base-image"
data-testid="image-test"
src="test.jpg"
tabindex="0"
title="test"
/>
</div>
<div
Expand Down Expand Up @@ -217,10 +218,11 @@ describe('Card', () => {
>
<img
alt="test"
class="str-chat__message-attachment--img"
class="str-chat__message-attachment--img str-chat__base-image"
data-testid="image-test"
src="test.jpg"
tabindex="0"
title="test"
/>
</div>
<div
Expand Down Expand Up @@ -296,10 +298,11 @@ describe('Card', () => {
>
<img
alt="test"
class="str-chat__message-attachment--img"
class="str-chat__message-attachment--img str-chat__base-image"
data-testid="image-test"
src="test.jpg"
tabindex="0"
title="test"
/>
</div>
</div>
Expand Down
45 changes: 30 additions & 15 deletions src/components/Attachment/__tests__/__snapshots__/Card.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@ exports[`Card theme V2 (1) should render card without caption if attachment type
>
<img
alt="dummyAttachment_title"
class="str-chat__message-attachment--img"
class="str-chat__message-attachment--img str-chat__base-image"
data-testid="image-test"
src="dummyAttachment_image_url"
tabindex="0"
title="dummyAttachment_title"
/>
</div>
<div
Expand Down Expand Up @@ -139,10 +140,11 @@ exports[`Card theme V2 (3) should render card without caption if attachment type
>
<img
alt="dummyAttachment_title"
class="str-chat__message-attachment--img"
class="str-chat__message-attachment--img str-chat__base-image"
data-testid="image-test"
src="dummyAttachment_image_url"
tabindex="0"
title="dummyAttachment_title"
/>
</div>
<div
Expand Down Expand Up @@ -232,10 +234,11 @@ exports[`Card theme V2 (7) should render audio with caption using og_scrape_url
>
<img
alt="dummyAttachment_title"
class="str-chat__message-attachment--img"
class="str-chat__message-attachment--img str-chat__base-image"
data-testid="image-test"
src="dummyAttachment_image_url"
tabindex="0"
title="dummyAttachment_title"
/>
</div>
<div
Expand Down Expand Up @@ -388,10 +391,11 @@ exports[`Card theme V2 (9) should render image with caption using og_scrape_url
>
<img
alt="dummyAttachment_title"
class="str-chat__message-attachment--img"
class="str-chat__message-attachment--img str-chat__base-image"
data-testid="image-test"
src="dummyAttachment_image_url"
tabindex="0"
title="dummyAttachment_title"
/>
</div>
<div
Expand Down Expand Up @@ -441,10 +445,11 @@ exports[`Card theme V2 (10) should render audio without title if attachment type
>
<img
alt="dummyAttachment_thumb_url"
class="str-chat__message-attachment--img"
class="str-chat__message-attachment--img str-chat__base-image"
data-testid="image-test"
src="dummyAttachment_image_url"
tabindex="0"
title="dummyAttachment_thumb_url"
/>
</div>
<div
Expand Down Expand Up @@ -587,10 +592,11 @@ exports[`Card theme V2 (12) should render image without title if attachment type
>
<img
alt="dummyAttachment_thumb_url"
class="str-chat__message-attachment--img"
class="str-chat__message-attachment--img str-chat__base-image"
data-testid="image-test"
src="dummyAttachment_image_url"
tabindex="0"
title="dummyAttachment_thumb_url"
/>
</div>
<div
Expand Down Expand Up @@ -635,10 +641,11 @@ exports[`Card theme V2 (13) should render audio without title and with caption u
>
<img
alt="dummyAttachment_thumb_url"
class="str-chat__message-attachment--img"
class="str-chat__message-attachment--img str-chat__base-image"
data-testid="image-test"
src="dummyAttachment_image_url"
tabindex="0"
title="dummyAttachment_thumb_url"
/>
</div>
<div
Expand Down Expand Up @@ -781,10 +788,11 @@ exports[`Card theme V2 (15) should render image without title and with caption u
>
<img
alt="dummyAttachment_thumb_url"
class="str-chat__message-attachment--img"
class="str-chat__message-attachment--img str-chat__base-image"
data-testid="image-test"
src="dummyAttachment_image_url"
tabindex="0"
title="dummyAttachment_thumb_url"
/>
</div>
<div
Expand Down Expand Up @@ -1014,10 +1022,11 @@ exports[`Card theme V2 (19) should render image loaded from thumb_url not audio
>
<img
alt="dummyAttachment_title"
class="str-chat__message-attachment--img"
class="str-chat__message-attachment--img str-chat__base-image"
data-testid="image-test"
src="dummyAttachment_thumb_url"
tabindex="0"
title="dummyAttachment_title"
/>
</div>
<div
Expand Down Expand Up @@ -1072,10 +1081,11 @@ exports[`Card theme V2 (20) should render image loaded from thumb_url not video
>
<img
alt="dummyAttachment_title"
class="str-chat__message-attachment--img"
class="str-chat__message-attachment--img str-chat__base-image"
data-testid="image-test"
src="dummyAttachment_thumb_url"
tabindex="0"
title="dummyAttachment_title"
/>
</div>
<div
Expand Down Expand Up @@ -1125,10 +1135,11 @@ exports[`Card theme V2 (21) should render image loaded from thumb_url if attachm
>
<img
alt="dummyAttachment_title"
class="str-chat__message-attachment--img"
class="str-chat__message-attachment--img str-chat__base-image"
data-testid="image-test"
src="dummyAttachment_thumb_url"
tabindex="0"
title="dummyAttachment_title"
/>
</div>
<div
Expand Down Expand Up @@ -1178,10 +1189,11 @@ exports[`Card theme V2 (22) should render image loaded from image_url not audio
>
<img
alt="dummyAttachment_title"
class="str-chat__message-attachment--img"
class="str-chat__message-attachment--img str-chat__base-image"
data-testid="image-test"
src="dummyAttachment_image_url"
tabindex="0"
title="dummyAttachment_title"
/>
</div>
<div
Expand Down Expand Up @@ -1236,10 +1248,11 @@ exports[`Card theme V2 (23) should render image loaded from image_url not video
>
<img
alt="dummyAttachment_title"
class="str-chat__message-attachment--img"
class="str-chat__message-attachment--img str-chat__base-image"
data-testid="image-test"
src="dummyAttachment_image_url"
tabindex="0"
title="dummyAttachment_title"
/>
</div>
<div
Expand Down Expand Up @@ -1289,10 +1302,11 @@ exports[`Card theme V2 (24) should render image loaded from image_url if attachm
>
<img
alt="dummyAttachment_title"
class="str-chat__message-attachment--img"
class="str-chat__message-attachment--img str-chat__base-image"
data-testid="image-test"
src="dummyAttachment_image_url"
tabindex="0"
title="dummyAttachment_title"
/>
</div>
<div
Expand Down Expand Up @@ -1342,10 +1356,11 @@ exports[`Card theme V2 (25) should render audio widget with image loaded from th
>
<img
alt="dummyAttachment_title"
class="str-chat__message-attachment--img"
class="str-chat__message-attachment--img str-chat__base-image"
data-testid="image-test"
src="dummyAttachment_image_url"
tabindex="0"
title="dummyAttachment_title"
/>
</div>
<div
Expand Down
5 changes: 4 additions & 1 deletion src/components/Channel/Channel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,9 @@ type ChannelPropsForwardedToComponentContext<
AutocompleteSuggestionList?: ComponentContextValue<StreamChatGenerics>['AutocompleteSuggestionList'];
/** 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) */
Avatar?: ComponentContextValue<StreamChatGenerics>['Avatar'];
/** 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) */
/** 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) */
BaseImage?: ComponentContextValue<StreamChatGenerics>['BaseImage'];
/** 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) */
CooldownTimer?: ComponentContextValue<StreamChatGenerics>['CooldownTimer'];
/** 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) */
DateSeparator?: ComponentContextValue<StreamChatGenerics>['DateSeparator'];
Expand Down Expand Up @@ -1013,6 +1015,7 @@ const ChannelInner = <
AutocompleteSuggestionItem: props.AutocompleteSuggestionItem,
AutocompleteSuggestionList: props.AutocompleteSuggestionList,
Avatar: props.Avatar,
BaseImage: props.BaseImage,
CooldownTimer: props.CooldownTimer,
DateSeparator: props.DateSeparator,
EditMessageInput: props.EditMessageInput,
Expand Down
Loading

0 comments on commit 6bcf06a

Please sign in to comment.