Skip to content

Commit de29a73

Browse files
committed
feat: adjust Gallery and Image widget for theming v2
1 parent 27e149a commit de29a73

File tree

9 files changed

+196
-101
lines changed

9 files changed

+196
-101
lines changed

src/components/Gallery/Gallery.tsx

Lines changed: 16 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1-
import React, { useMemo, useState } from 'react';
1+
import React, { useState } from 'react';
2+
import clsx from 'clsx';
23

3-
import { ModalComponent as ModalWrapper } from './ModalWrapper';
4+
import { Modal } from '../Modal';
5+
import { ModalGallery as DefaultModalGallery } from './ModalGallery';
46

7+
import { useComponentContext } from '../../context/ComponentContext';
58
import { useTranslationContext } from '../../context/TranslationContext';
69

710
import type { Attachment } from 'stream-chat';
@@ -29,6 +32,7 @@ const UnMemoizedGallery = <
2932
const [index, setIndex] = useState(0);
3033
const [modalOpen, setModalOpen] = useState(false);
3134

35+
const { ModalGallery = DefaultModalGallery } = useComponentContext('Gallery');
3236
const { t } = useTranslationContext('Gallery');
3337

3438
const countImagesDisplayedInPreview = 4;
@@ -43,16 +47,6 @@ const UnMemoizedGallery = <
4347
}
4448
};
4549

46-
const formattedArray = useMemo(
47-
() =>
48-
images.map((image) => ({
49-
original: image.image_url || image.thumb_url || '',
50-
originalAlt: 'User uploaded content',
51-
source: image.image_url || image.thumb_url || '',
52-
})),
53-
[images],
54-
);
55-
5650
const renderImages = images.slice(0, countImagesDisplayedInPreview).map((image, i) =>
5751
i === lastImageIndexInPreview && images.length > countImagesDisplayedInPreview ? (
5852
<button
@@ -81,19 +75,18 @@ const UnMemoizedGallery = <
8175
),
8276
);
8377

78+
const className = clsx(
79+
'str-chat__gallery',
80+
images.length > lastImageIndexInPreview && 'str-chat__gallery--square',
81+
images.length > 2 && 'str-chat__gallery-two-rows',
82+
);
83+
8484
return (
85-
<div
86-
className={`str-chat__gallery ${
87-
images.length > lastImageIndexInPreview ? 'str-chat__gallery--square' : ''
88-
}`}
89-
>
85+
<div className={className}>
9086
{renderImages}
91-
<ModalWrapper
92-
images={formattedArray}
93-
index={index}
94-
modalIsOpen={modalOpen}
95-
toggleModal={() => setModalOpen(!modalOpen)}
96-
/>
87+
<Modal onClose={() => setModalOpen((modalOpen) => !modalOpen)} open={modalOpen}>
88+
<ModalGallery images={images} index={index} />
89+
</Modal>
9790
</div>
9891
);
9992
};

src/components/Gallery/Image.tsx

Lines changed: 32 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,42 @@
11
import React, { useState } from 'react';
22
import { sanitizeUrl } from '@braintree/sanitize-url';
33

4-
import { ModalComponent as ModalWrapper } from './ModalWrapper';
5-
6-
export type ImageProps = {
7-
/** The text fallback for the image */
8-
fallback?: string;
9-
/** The full size image url */
10-
image_url?: string;
11-
/** The thumb url */
12-
thumb_url?: string;
13-
};
4+
import { Modal } from '../Modal';
5+
import { ModalGallery as DefaultModalGallery } from './ModalGallery';
6+
import { useComponentContext } from '../../context';
7+
8+
import type { Attachment } from 'stream-chat';
9+
import type { DefaultStreamChatGenerics } from '../../types/types';
10+
11+
export type ImageProps<
12+
StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics
13+
> =
14+
| {
15+
/** The text fallback for the image */
16+
fallback?: string;
17+
/** The full size image url */
18+
image_url?: string;
19+
/** The thumb url */
20+
thumb_url?: string;
21+
}
22+
| Attachment<StreamChatGenerics>;
1423

1524
/**
1625
* A simple component that displays an image.
1726
*/
18-
export const ImageComponent = (props: ImageProps) => {
27+
export const ImageComponent = <
28+
StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics
29+
>(
30+
props: ImageProps<StreamChatGenerics>,
31+
) => {
32+
const { fallback, image_url, thumb_url } = props;
33+
1934
const [modalIsOpen, setModalIsOpen] = useState(false);
35+
const { ModalGallery = DefaultModalGallery } = useComponentContext('ImageComponent');
2036

21-
const { fallback, image_url, thumb_url } = props;
2237
const imageSrc = sanitizeUrl(image_url || thumb_url);
23-
const formattedArray = [
24-
{ original: imageSrc, originalAlt: 'User uploaded content', source: imageSrc },
25-
];
2638

27-
const toggleModal = () => setModalIsOpen(!modalIsOpen);
39+
const toggleModal = () => setModalIsOpen((modalIsOpen) => !modalIsOpen);
2840

2941
return (
3042
<>
@@ -33,17 +45,13 @@ export const ImageComponent = (props: ImageProps) => {
3345
className='str-chat__message-attachment--img'
3446
data-testid='image-test'
3547
onClick={toggleModal}
36-
onKeyPress={toggleModal}
48+
onKeyUp={toggleModal}
3749
src={imageSrc}
3850
tabIndex={0}
3951
/>
40-
41-
<ModalWrapper
42-
images={formattedArray}
43-
index={0}
44-
modalIsOpen={modalIsOpen}
45-
toggleModal={toggleModal}
46-
/>
52+
<Modal onClose={toggleModal} open={modalIsOpen}>
53+
<ModalGallery images={[props]} index={0} />
54+
</Modal>
4755
</>
4856
);
4957
};
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import React, { useMemo } from 'react';
2+
import ImageGallery from 'react-image-gallery';
3+
4+
import type { Attachment } from 'stream-chat';
5+
import type { DefaultStreamChatGenerics } from '../../types/types';
6+
7+
export type ModalGalleryProps<
8+
StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics
9+
> = {
10+
/** The images for the Carousel component */
11+
images: Attachment<StreamChatGenerics>[];
12+
/** The index for the component */
13+
index?: number;
14+
};
15+
16+
export const ModalGallery = <
17+
StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics
18+
>(
19+
props: ModalGalleryProps<StreamChatGenerics>,
20+
) => {
21+
const { images, index } = props;
22+
23+
const formattedArray = useMemo(
24+
() =>
25+
images.map((image) => {
26+
const imageSrc = image.image_url || image.thumb_url || '';
27+
return {
28+
original: imageSrc,
29+
originalAlt: 'User uploaded content',
30+
source: imageSrc,
31+
};
32+
}),
33+
[images],
34+
);
35+
36+
return (
37+
<ImageGallery
38+
items={formattedArray}
39+
showIndex={true}
40+
showPlayButton={false}
41+
showThumbnails={false}
42+
startIndex={index}
43+
/>
44+
);
45+
};

src/components/Gallery/ModalWrapper.tsx

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

src/components/Gallery/__tests__/Gallery.test.js

Lines changed: 61 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import React from 'react';
2+
import { nanoid } from 'nanoid';
23
import renderer from 'react-test-renderer';
34
import { act, cleanup, fireEvent, render, waitFor } from '@testing-library/react';
45
import '@testing-library/jest-dom';
@@ -8,6 +9,8 @@ import { getTestClientWithUser } from '../../../mock-builders';
89
import { Chat } from '../../Chat';
910
import { Gallery } from '../Gallery';
1011

12+
import { ComponentProvider } from '../../../context/ComponentContext';
13+
1114
let chatClient;
1215

1316
const mockGalleryAssets = [
@@ -38,33 +41,62 @@ const mockGalleryAssets = [
3841
},
3942
];
4043

41-
afterEach(cleanup); // eslint-disable-line
42-
4344
describe('Gallery', () => {
45+
afterEach(cleanup);
46+
4447
it('should render component with default props', () => {
45-
const tree = renderer.create(<Gallery images={mockGalleryAssets.slice(0, 2)} />).toJSON();
48+
const tree = renderer
49+
.create(
50+
<ComponentProvider value={{}}>
51+
<Gallery images={mockGalleryAssets.slice(0, 2)} />
52+
</ComponentProvider>,
53+
)
54+
.toJSON();
4655
expect(tree).toMatchSnapshot();
4756
});
4857

4958
it('should render component with 3 images', () => {
50-
const tree = renderer.create(<Gallery images={mockGalleryAssets.slice(0, 3)} />).toJSON();
59+
const tree = renderer
60+
.create(
61+
<ComponentProvider value={{}}>
62+
<Gallery images={mockGalleryAssets.slice(0, 3)} />
63+
</ComponentProvider>,
64+
)
65+
.toJSON();
5166
expect(tree).toMatchSnapshot();
5267
});
5368

5469
it('should render component with 4 images', () => {
55-
const tree = renderer.create(<Gallery images={mockGalleryAssets.slice(0, 4)} />).toJSON();
70+
const tree = renderer
71+
.create(
72+
<ComponentProvider value={{}}>
73+
<Gallery images={mockGalleryAssets.slice(0, 4)} />
74+
</ComponentProvider>,
75+
)
76+
.toJSON();
5677
expect(tree).toMatchSnapshot();
5778
});
5879

5980
it('should render component with 5 images', () => {
60-
const tree = renderer.create(<Gallery images={mockGalleryAssets} />).toJSON();
81+
const tree = renderer
82+
.create(
83+
<ComponentProvider value={{}}>
84+
<Gallery images={mockGalleryAssets} />
85+
</ComponentProvider>,
86+
)
87+
.toJSON();
6188
expect(tree).toMatchSnapshot();
6289
});
6390

6491
it('should open modal on image click', async () => {
6592
jest.spyOn(console, 'warn').mockImplementation(() => null);
6693

67-
const { getByTestId, getByTitle } = render(<Gallery images={mockGalleryAssets.slice(0, 1)} />);
94+
const { getByTestId, getByTitle } = render(
95+
<ComponentProvider value={{}}>
96+
<Gallery images={mockGalleryAssets.slice(0, 1)} />
97+
</ComponentProvider>,
98+
);
99+
68100
fireEvent.click(getByTestId('gallery-image'));
69101

70102
await waitFor(() => {
@@ -76,7 +108,9 @@ describe('Gallery', () => {
76108
chatClient = await getTestClientWithUser({ id: 'test' });
77109
const { getByText } = await render(
78110
<Chat client={chatClient}>
79-
<Gallery images={mockGalleryAssets} />,
111+
<ComponentProvider value={{}}>
112+
<Gallery images={mockGalleryAssets} />
113+
</ComponentProvider>
80114
</Chat>,
81115
);
82116
await waitFor(() => {
@@ -88,7 +122,9 @@ describe('Gallery', () => {
88122
chatClient = await getTestClientWithUser({ id: 'test' });
89123
const { container, getByText } = render(
90124
<Chat client={chatClient}>
91-
<Gallery images={mockGalleryAssets} />,
125+
<ComponentProvider value={{}}>
126+
<Gallery images={mockGalleryAssets} />
127+
</ComponentProvider>
92128
</Chat>,
93129
);
94130

@@ -101,4 +137,20 @@ describe('Gallery', () => {
101137
expect(container.querySelector('.image-gallery-index')).toHaveTextContent('4 / 5');
102138
});
103139
});
140+
141+
it('should render custom ModalGallery component from context', async () => {
142+
const galleryContent = nanoid();
143+
const CustomGallery = () => <div>{galleryContent}</div>;
144+
const { getAllByTestId, getByText } = render(
145+
<ComponentProvider value={{ ModalGallery: CustomGallery }}>
146+
<Gallery images={mockGalleryAssets} />
147+
</ComponentProvider>,
148+
);
149+
150+
fireEvent.click(getAllByTestId('gallery-image')[0]);
151+
152+
await waitFor(() => {
153+
expect(getByText(galleryContent)).toBeInTheDocument();
154+
});
155+
});
104156
});

0 commit comments

Comments
 (0)