Skip to content

Commit 0582edc

Browse files
authored
feat: Add ImageGrid ui component. (#703)
Add ImageGrid ui component. Fixes: https://sendbird.atlassian.net/browse/UIKIT-4287
1 parent 3d3a8bd commit 0582edc

File tree

9 files changed

+231
-1
lines changed

9 files changed

+231
-1
lines changed
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import React from 'react';
2+
import ImageGrid from "../index";
3+
import { render } from '@testing-library/react';
4+
import ImageRenderer from "../../ImageRenderer";
5+
import Icon, {IconColors, IconTypes} from "../../Icon";
6+
7+
const PROFILE_IMAGE = "https://static.sendbird.com/sample/profiles/profile_12_512px.png";
8+
const EARTH_IMAGE = "https://sendbird-upload.s3.amazonaws.com/2D7B4CDB-932F-4082-9B09-A1153792DC8D/" +
9+
"upload/n/8af7775ca1d34d7681d7e61b56067136.jpg";
10+
11+
const imageRenderers = [
12+
<ImageRenderer
13+
url={PROFILE_IMAGE}
14+
width='200px'
15+
height='200px'
16+
borderRadius='6px'
17+
defaultComponent={
18+
<Icon type={IconTypes.ADD} fillColor={IconColors.PRIMARY} />
19+
}
20+
/>,
21+
<ImageRenderer
22+
url={EARTH_IMAGE}
23+
width='200px'
24+
height='200px'
25+
borderRadius='6px'
26+
defaultComponent={
27+
<Icon type={IconTypes.ADD} fillColor={IconColors.PRIMARY} />
28+
}
29+
/>,
30+
<ImageRenderer
31+
url={EARTH_IMAGE}
32+
width='200px'
33+
height='200px'
34+
borderRadius='6px'
35+
defaultComponent={
36+
<Icon type={IconTypes.ADD} fillColor={IconColors.PRIMARY} />
37+
}
38+
/>,
39+
];
40+
41+
describe('ui/ImageGrid', () => {
42+
it('should do a snapshot test of the ImageGrid DOM', function() {
43+
const { asFragment } = render(
44+
<ImageGrid items={imageRenderers} />
45+
);
46+
expect(asFragment()).toMatchSnapshot();
47+
});
48+
});
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`ui/ImageGrid should do a snapshot test of the ImageGrid DOM 1`] = `
4+
<DocumentFragment>
5+
<div
6+
class="sendbird-image-grid-wrap"
7+
>
8+
<div
9+
class="sendbird-image-grid"
10+
/>
11+
</div>
12+
</DocumentFragment>
13+
`;

src/ui/ImageGrid/index.scss

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
@import '../../styles/variables';
2+
3+
.sendbird-image-grid-wrap {
4+
display: flex;
5+
justify-content: flex-end;
6+
}
7+
8+
.sendbird-image-grid {
9+
border-radius: 12px;
10+
direction: rtl;
11+
display: grid;
12+
gap: 4px;
13+
grid-template-columns: repeat(2, 1fr);
14+
padding: 4px;
15+
width: fit-content;
16+
@include themed() {
17+
background-color: t(bg-1);
18+
}
19+
}

src/ui/ImageGrid/index.tsx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import React, { ReactElement } from 'react';
2+
import './index.scss';
3+
4+
interface ImageGridProps {
5+
children: ReactElement[];
6+
}
7+
8+
export default function ImageGrid({
9+
children,
10+
}: ImageGridProps): ReactElement {
11+
return (
12+
<div className='sendbird-image-grid-wrap'>
13+
<div className='sendbird-image-grid'>
14+
{ children }
15+
</div>
16+
</div>
17+
);
18+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import React from 'react';
2+
import ImageGrid from '../index.tsx';
3+
import ImageRenderer from "../../ImageRenderer";
4+
import Icon, {IconColors, IconTypes} from "../../Icon";
5+
6+
const PROFILE_IMAGE = "https://static.sendbird.com/sample/profiles/profile_12_512px.png";
7+
const EARTH_IMAGE = "https://sendbird-upload.s3.amazonaws.com/2D7B4CDB-932F-4082-9B09-A1153792DC8D/" +
8+
"upload/n/8af7775ca1d34d7681d7e61b56067136.jpg";
9+
10+
const description = `
11+
\`import ImageGrid from "@sendbird/uikit-react/ui/ImageGrid";\`
12+
`;
13+
14+
export default {
15+
title: '@sendbird/uikit-react/ui/ImageGrid',
16+
component: ImageGrid,
17+
parameters: {
18+
docs: {
19+
description: {
20+
component: description,
21+
},
22+
},
23+
},
24+
};
25+
26+
export const withImages = () => {
27+
const imageRenderers = [
28+
<ImageRenderer
29+
url={PROFILE_IMAGE}
30+
width='200px'
31+
height='200px'
32+
borderRadius='6px'
33+
defaultComponent={
34+
<Icon type={IconTypes.ADD} fillColor={IconColors.PRIMARY} />
35+
}
36+
/>,
37+
<ImageRenderer
38+
url={EARTH_IMAGE}
39+
width='200px'
40+
height='200px'
41+
borderRadius='6px'
42+
defaultComponent={
43+
<Icon type={IconTypes.ADD} fillColor={IconColors.PRIMARY} />
44+
}
45+
/>,
46+
<ImageRenderer
47+
url={EARTH_IMAGE}
48+
width='200px'
49+
height='200px'
50+
borderRadius='6px'
51+
defaultComponent={
52+
<Icon type={IconTypes.ADD} fillColor={IconColors.PRIMARY} />
53+
}
54+
/>,
55+
<ImageRenderer
56+
url={PROFILE_IMAGE}
57+
width='200px'
58+
height='200px'
59+
borderRadius='6px'
60+
defaultComponent={
61+
<Icon type={IconTypes.ADD} fillColor={IconColors.PRIMARY} />
62+
}
63+
/>,
64+
<ImageRenderer
65+
url={PROFILE_IMAGE}
66+
width='200px'
67+
height='200px'
68+
borderRadius='6px'
69+
defaultComponent={
70+
<Icon type={IconTypes.ADD} fillColor={IconColors.PRIMARY} />
71+
}
72+
/>,
73+
];
74+
return <ImageGrid>{ imageRenderers }</ImageGrid>;
75+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { getBorderRadiusForImageRenderer } from '../index';
2+
3+
describe('Global-utils/getBorderRadiusForImageRenderer', () => {
4+
it('when given nothing, return null', () => {
5+
expect(getBorderRadiusForImageRenderer(undefined, undefined)).toBeNull();
6+
});
7+
it('when given borderRadius=12 only, return 12px', () => {
8+
expect(getBorderRadiusForImageRenderer(undefined, 12)).toBe('12px');
9+
});
10+
it('when given borderRadius=12px only, return 12px', () => {
11+
expect(getBorderRadiusForImageRenderer(undefined, '12px')).toBe('12px');
12+
});
13+
it('when given circle=false only, return null', () => {
14+
expect(getBorderRadiusForImageRenderer(false, undefined)).toBeNull();
15+
});
16+
it('when given circle=false and borderRadius=12, return 12px', () => {
17+
expect(getBorderRadiusForImageRenderer(false, 12)).toBe('12px');
18+
});
19+
it('when given circle=false and borderRadius=12px, return 12px', () => {
20+
expect(getBorderRadiusForImageRenderer(false, '12px')).toBe('12px');
21+
});
22+
it('when given circle=true only, return 50%', () => {
23+
expect(getBorderRadiusForImageRenderer(true, undefined)).toBe('50%');
24+
});
25+
it('when given circle=true and borderRadius=12, return 50%', () => {
26+
expect(getBorderRadiusForImageRenderer(true, 12)).toBe('50%');
27+
});
28+
it('when given circle=true and borderRadius=12px, return 50%', () => {
29+
expect(getBorderRadiusForImageRenderer(true, '12px')).toBe('50%');
30+
});
31+
});

src/ui/ImageRenderer/index.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React, { useState, useMemo, ReactElement } from 'react';
22

33
import './index.scss';
4+
import numberToPx from '../../utils/numberToPx';
45

56
/*
67
ImageRenderer displays image with url or source
@@ -9,6 +10,13 @@ import './index.scss';
910
and those properties switch img tag to real purposing element
1011
*/
1112

13+
export function getBorderRadiusForImageRenderer(
14+
circle = false,
15+
borderRadius: string | number = null,
16+
): string {
17+
return circle ? '50%' : numberToPx(borderRadius);
18+
}
19+
1220
export interface ImageRendererProps {
1321
className?: string | Array<string>;
1422
url: string;
@@ -19,6 +27,7 @@ export interface ImageRendererProps {
1927
fixedSize?: boolean;
2028
placeHolder?: ((props: { style: { [key: string]: string | number } }) => ReactElement) | ReactElement;
2129
defaultComponent?: (() => ReactElement) | ReactElement;
30+
borderRadius?: string | number;
2231
onLoad?: () => void;
2332
onError?: () => void;
2433
}
@@ -33,6 +42,7 @@ const ImageRenderer = ({
3342
fixedSize = false,
3443
placeHolder = null,
3544
defaultComponent = null,
45+
borderRadius = null,
3646
onLoad = () => { /* noop */ },
3747
onError = () => { /* noop */ },
3848
}: ImageRendererProps): ReactElement => {
@@ -112,7 +122,7 @@ const ImageRenderer = ({
112122
backgroundPosition: 'center',
113123
backgroundSize: 'cover',
114124
backgroundImage: `url(${url})`,
115-
borderRadius: circle ? '50%' : null,
125+
borderRadius: getBorderRadiusForImageRenderer(circle, borderRadius),
116126
}}
117127
/>
118128
)
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import numberToPx from '../numberToPx';
2+
3+
describe('Global-utils/numberToPx', () => {
4+
it('when given number, should return pixel string.', () => {
5+
expect(numberToPx(12)).toBe('12px');
6+
});
7+
it('when given pixel string, should return pixel string.', () => {
8+
expect(numberToPx('12px')).toBe('12px');
9+
});
10+
it('when given random string, should return random string.', () => {
11+
expect(numberToPx('wefwefwef323')).toBe('wefwefwef323');
12+
});
13+
});

src/utils/numberToPx.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export default (value: string | number): string => {
2+
return typeof value === 'number' ? `${value}px` : value;
3+
};

0 commit comments

Comments
 (0)