diff --git a/ts/components/conversation/ImageGrid.md b/ts/components/conversation/ImageGrid.md
deleted file mode 100644
index 4835948e72..0000000000
--- a/ts/components/conversation/ImageGrid.md
+++ /dev/null
@@ -1,409 +0,0 @@
-### One image
-
-```jsx
-const attachments = [
- {
- url: util.gifObjectUrl,
- contentType: 'image/gif',
- width: 320,
- height: 240,
- },
-];
-
-
;
-```
-
-### One image, various aspect ratios
-
-```jsx
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-```
-
-### Two images
-
-```jsx
-const attachments = [
- {
- url: util.pngObjectUrl,
- contentType: 'image/png',
- width: 320,
- height: 240,
- },
- {
- url: util.pngObjectUrl,
- contentType: 'image/png',
- width: 320,
- height: 240,
- },
-];
-
-;
-```
-
-### Three images
-
-```jsx
-const attachments = [
- {
- url: util.pngObjectUrl,
- contentType: 'image/png',
- width: 320,
- height: 240,
- },
- {
- url: util.pngObjectUrl,
- contentType: 'image/png',
- width: 320,
- height: 240,
- },
- {
- url: util.pngObjectUrl,
- contentType: 'image/png',
- width: 320,
- height: 240,
- },
-];
-
-;
-```
-
-### Four images
-
-```jsx
-const attachments = [
- {
- url: util.pngObjectUrl,
- contentType: 'image/png',
- width: 320,
- height: 240,
- },
- {
- url: util.pngObjectUrl,
- contentType: 'image/png',
- width: 320,
- height: 240,
- },
- {
- url: util.pngObjectUrl,
- contentType: 'image/png',
- width: 320,
- height: 240,
- },
- {
- url: util.pngObjectUrl,
- contentType: 'image/png',
- width: 320,
- height: 240,
- },
-];
-
-;
-```
-
-### Five images
-
-```jsx
-const attachments = [
- {
- url: util.pngObjectUrl,
- contentType: 'image/png',
- width: 320,
- height: 240,
- },
- {
- url: util.pngObjectUrl,
- contentType: 'image/png',
- width: 320,
- height: 240,
- },
- {
- url: util.pngObjectUrl,
- contentType: 'image/png',
- width: 320,
- height: 240,
- },
- {
- url: util.pngObjectUrl,
- contentType: 'image/png',
- width: 320,
- height: 240,
- },
- {
- url: util.pngObjectUrl,
- contentType: 'image/png',
- width: 320,
- height: 240,
- },
-];
-
-;
-```
-
-### Six images
-
-```
-const attachments = [
- {
- url: util.pngObjectUrl,
- contentType: 'image/png',
- width: 320,
- height: 240,
- },
- {
- url: util.pngObjectUrl,
- contentType: 'image/png',
- width: 320,
- height: 240,
- },
- {
- url: util.pngObjectUrl,
- contentType: 'image/png',
- width: 320,
- height: 240,
- },
- {
- url: util.pngObjectUrl,
- contentType: 'image/png',
- width: 320,
- height: 240,
- },
- {
- url: util.pngObjectUrl,
- contentType: 'image/png',
- width: 320,
- height: 240,
- },
- {
- url: util.pngObjectUrl,
- contentType: 'image/png',
- width: 320,
- height: 240,
- },
-];
-
-;
-```
-
-### Mixing attachment types
-
-```
-const attachments = [
- {
- url: util.pngObjectUrl,
- contentType: 'image/png',
- width: 320,
- height: 240,
- },
- {
- contentType: 'text/plain',
- },
- {
- url: util.pngObjectUrl,
- contentType: 'image/png',
- width: 320,
- height: 240,
- },
-];
-
-;
-```
-
-### Sticker
-
-```
-const attachments = [
- {
- url: util.squareStickerObjectUrl,
- contentType: 'image/webp',
- width: 512,
- height: 512,
- },
-];
-
-;
-```
diff --git a/ts/components/conversation/ImageGrid.stories.tsx b/ts/components/conversation/ImageGrid.stories.tsx
new file mode 100644
index 0000000000..990b5f79cc
--- /dev/null
+++ b/ts/components/conversation/ImageGrid.stories.tsx
@@ -0,0 +1,322 @@
+import * as React from 'react';
+
+import { action } from '@storybook/addon-actions';
+import { boolean, number } from '@storybook/addon-knobs';
+import { storiesOf } from '@storybook/react';
+
+import { ImageGrid, Props } from './ImageGrid';
+import {
+ AUDIO_MP3,
+ IMAGE_JPEG,
+ IMAGE_PNG,
+ IMAGE_WEBP,
+ MIMEType,
+ VIDEO_MP4,
+} from '../../types/MIME';
+
+// @ts-ignore
+import { setup as setupI18n } from '../../../js/modules/i18n';
+// @ts-ignore
+import enMessages from '../../../_locales/en/messages.json';
+import { pngUrl, squareStickerUrl } from '../../storybook/Fixtures';
+const i18n = setupI18n('en', enMessages);
+
+const story = storiesOf('Components/Conversation/ImageGrid', module);
+
+const createProps = (overrideProps: Partial = {}): Props => ({
+ attachments: overrideProps.attachments || [
+ {
+ contentType: IMAGE_PNG,
+ fileName: 'sax.png',
+ height: 1200,
+ url: pngUrl,
+ width: 800,
+ },
+ ],
+ bottomOverlay: boolean('bottomOverlay', overrideProps.bottomOverlay || false),
+ i18n,
+ isSticker: boolean('isSticker', overrideProps.isSticker || false),
+ onClick: action('onClick'),
+ onError: action('onError'),
+ stickerSize: number('stickerSize', overrideProps.stickerSize || 0),
+ tabIndex: number('tabIndex', overrideProps.tabIndex || 0),
+ withContentAbove: boolean(
+ 'withContentAbove',
+ overrideProps.withContentAbove || false
+ ),
+ withContentBelow: boolean(
+ 'withContentBelow',
+ overrideProps.withContentBelow || false
+ ),
+});
+
+story.add('One Image', () => {
+ const props = createProps();
+
+ return ;
+});
+
+story.add('Two Images', () => {
+ const props = createProps({
+ attachments: [
+ {
+ contentType: IMAGE_PNG,
+ fileName: 'sax.png',
+ height: 1200,
+ url: pngUrl,
+ width: 800,
+ },
+ {
+ contentType: IMAGE_JPEG,
+ fileName: 'tina-rolf-269345-unsplash.jpg',
+ height: 1680,
+ url: '/fixtures/tina-rolf-269345-unsplash.jpg',
+ width: 3000,
+ },
+ ],
+ });
+
+ return ;
+});
+
+story.add('Three Images', () => {
+ const props = createProps({
+ attachments: [
+ {
+ contentType: IMAGE_PNG,
+ fileName: 'sax.png',
+ height: 1200,
+ url: pngUrl,
+ width: 800,
+ },
+ {
+ contentType: IMAGE_JPEG,
+ fileName: 'tina-rolf-269345-unsplash.jpg',
+ height: 1680,
+ url: '/fixtures/tina-rolf-269345-unsplash.jpg',
+ width: 3000,
+ },
+ {
+ contentType: IMAGE_PNG,
+ fileName: 'sax.png',
+ height: 1200,
+ url: pngUrl,
+ width: 800,
+ },
+ ],
+ });
+
+ return ;
+});
+
+story.add('Four Images', () => {
+ const props = createProps({
+ attachments: [
+ {
+ contentType: IMAGE_PNG,
+ fileName: 'sax.png',
+ height: 1200,
+ url: pngUrl,
+ width: 800,
+ },
+ {
+ contentType: IMAGE_JPEG,
+ fileName: 'tina-rolf-269345-unsplash.jpg',
+ height: 1680,
+ url: '/fixtures/tina-rolf-269345-unsplash.jpg',
+ width: 3000,
+ },
+ {
+ contentType: IMAGE_PNG,
+ fileName: 'sax.png',
+ height: 1200,
+ url: pngUrl,
+ width: 800,
+ },
+ {
+ contentType: IMAGE_JPEG,
+ fileName: 'tina-rolf-269345-unsplash.jpg',
+ height: 1680,
+ url: '/fixtures/tina-rolf-269345-unsplash.jpg',
+ width: 3000,
+ },
+ ],
+ });
+
+ return ;
+});
+
+story.add('Five Images', () => {
+ const props = createProps({
+ attachments: [
+ {
+ contentType: IMAGE_PNG,
+ fileName: 'sax.png',
+ height: 1200,
+ url: pngUrl,
+ width: 800,
+ },
+ {
+ contentType: IMAGE_JPEG,
+ fileName: 'tina-rolf-269345-unsplash.jpg',
+ height: 1680,
+ url: '/fixtures/tina-rolf-269345-unsplash.jpg',
+ width: 3000,
+ },
+ {
+ contentType: IMAGE_PNG,
+ fileName: 'sax.png',
+ height: 1200,
+ url: pngUrl,
+ width: 800,
+ },
+ {
+ contentType: IMAGE_JPEG,
+ fileName: 'tina-rolf-269345-unsplash.jpg',
+ height: 1680,
+ url: '/fixtures/tina-rolf-269345-unsplash.jpg',
+ width: 3000,
+ },
+ {
+ contentType: IMAGE_PNG,
+ fileName: 'sax.png',
+ height: 1200,
+ url: pngUrl,
+ width: 800,
+ },
+ ],
+ });
+
+ return ;
+});
+
+story.add('6+ Images', () => {
+ const props = createProps({
+ attachments: [
+ {
+ contentType: IMAGE_PNG,
+ fileName: 'sax.png',
+ height: 1200,
+ url: pngUrl,
+ width: 800,
+ },
+ {
+ contentType: IMAGE_JPEG,
+ fileName: 'tina-rolf-269345-unsplash.jpg',
+ height: 1680,
+ url: '/fixtures/tina-rolf-269345-unsplash.jpg',
+ width: 3000,
+ },
+ {
+ contentType: IMAGE_PNG,
+ fileName: 'sax.png',
+ height: 1200,
+ url: pngUrl,
+ width: 800,
+ },
+ {
+ contentType: IMAGE_JPEG,
+ fileName: 'tina-rolf-269345-unsplash.jpg',
+ height: 1680,
+ url: '/fixtures/tina-rolf-269345-unsplash.jpg',
+ width: 3000,
+ },
+ {
+ contentType: IMAGE_PNG,
+ fileName: 'sax.png',
+ height: 1200,
+ url: pngUrl,
+ width: 800,
+ },
+ {
+ contentType: IMAGE_PNG,
+ fileName: 'sax.png',
+ height: 1200,
+ url: pngUrl,
+ width: 800,
+ },
+ {
+ contentType: IMAGE_PNG,
+ fileName: 'sax.png',
+ height: 1200,
+ url: pngUrl,
+ width: 800,
+ },
+ ],
+ });
+
+ return ;
+});
+story.add('Mixed Content Types', () => {
+ const props = createProps({
+ attachments: [
+ {
+ contentType: VIDEO_MP4,
+ fileName: 'pixabay-Soap-Bubble-7141.mp4',
+ height: 112,
+ screenshot: {
+ height: 112,
+ width: 112,
+ url: '/fixtures/kitten-4-112-112.jpg',
+ contentType: IMAGE_JPEG,
+ },
+ url: '/fixtures/pixabay-Soap-Bubble-7141.mp4',
+ width: 112,
+ },
+ {
+ contentType: IMAGE_PNG,
+ fileName: 'sax.png',
+ height: 1200,
+ url: pngUrl,
+ width: 800,
+ },
+ {
+ contentType: 'text/plain' as MIMEType,
+ fileName: 'lorem-ipsum.txt',
+ url: '/fixtures/lorem-ipsum.txt',
+ },
+ {
+ contentType: AUDIO_MP3,
+ fileName: 'incompetech-com-Agnus-Dei-X.mp3',
+ url: '/fixtures/incompetech-com-Agnus-Dei-X.mp3',
+ },
+ ],
+ });
+
+ return ;
+});
+
+story.add('Sticker', () => {
+ const props = createProps({
+ attachments: [
+ {
+ contentType: IMAGE_WEBP,
+ fileName: 'sticker.webp',
+ height: 512,
+ url: squareStickerUrl,
+ width: 512,
+ },
+ ],
+ isSticker: true,
+ stickerSize: 128,
+ });
+
+ return ;
+});
+
+story.add('Content Above and Below', () => {
+ const props = createProps({
+ withContentAbove: true,
+ withContentBelow: true,
+ });
+
+ return ;
+});
+
+story.add('Bottom Overlay', () => {
+ const props = createProps({
+ bottomOverlay: true,
+ });
+
+ return ;
+});
diff --git a/ts/components/conversation/ImageGrid.tsx b/ts/components/conversation/ImageGrid.tsx
index 2162d53e50..78819d2f25 100644
--- a/ts/components/conversation/ImageGrid.tsx
+++ b/ts/components/conversation/ImageGrid.tsx
@@ -15,7 +15,7 @@ import { Image } from './Image';
import { LocalizerType } from '../../types/Util';
-interface Props {
+export interface Props {
attachments: Array;
withContentAbove?: boolean;
withContentBelow?: boolean;
diff --git a/ts/types/MIME.ts b/ts/types/MIME.ts
index 5b7420051f..994bff32e8 100644
--- a/ts/types/MIME.ts
+++ b/ts/types/MIME.ts
@@ -8,6 +8,7 @@ export const IMAGE_GIF = 'image/gif' as MIMEType;
export const IMAGE_JPEG = 'image/jpeg' as MIMEType;
export const IMAGE_PNG = 'image/png' as MIMEType;
export const IMAGE_WEBP = 'image/webp' as MIMEType;
+export const IMAGE_PNG = 'image/png' as MIMEType;
export const VIDEO_MP4 = 'video/mp4' as MIMEType;
export const VIDEO_QUICKTIME = 'video/quicktime' as MIMEType;
export const LONG_MESSAGE = 'text/x-signal-plain' as MIMEType;