Fix collapsed corners for link previews and image attachments

Co-authored-by: Scott Nonnenberg <scott@signal.org>
This commit is contained in:
automated-signal 2022-04-27 16:43:03 -07:00 committed by GitHub
parent 050a1bf789
commit 653e885266
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 260 additions and 181 deletions

View File

@ -779,7 +779,7 @@
width: calc(100% + 24px); width: calc(100% + 24px);
outline: none; outline: none;
margin-top: -10px; margin-top: -8px;
margin-bottom: 5px; margin-bottom: 5px;
overflow: hidden; overflow: hidden;
@ -2589,30 +2589,10 @@ button.ConversationDetails__action-button {
left: 6px; left: 6px;
} }
.module-image--soft-corners {
border-radius: 4px;
}
.module-image--cropped { .module-image--cropped {
overflow: hidden; overflow: hidden;
} }
.module-image--curved-top-left {
border-top-left-radius: 18px;
}
.module-image--curved-top-right {
border-top-right-radius: 18px;
}
.module-image--curved-bottom-left {
border-bottom-left-radius: 18px;
}
.module-image--curved-bottom-right {
border-bottom-right-radius: 18px;
}
.module-image--small-curved-top-left {
border-top-left-radius: 10px;
}
.module-image__border-overlay { .module-image__border-overlay {
@include button-reset; @include button-reset;

View File

@ -3,7 +3,7 @@
import React from 'react'; import React from 'react';
import { Image } from './Image'; import { CurveType, Image } from './Image';
import { StagedGenericAttachment } from './StagedGenericAttachment'; import { StagedGenericAttachment } from './StagedGenericAttachment';
import { StagedPlaceholderAttachment } from './StagedPlaceholderAttachment'; import { StagedPlaceholderAttachment } from './StagedPlaceholderAttachment';
import type { LocalizerType } from '../../types/Util'; import type { LocalizerType } from '../../types/Util';
@ -109,7 +109,10 @@ export const AttachmentList = <T extends AttachmentType | AttachmentDraftType>({
i18n={i18n} i18n={i18n}
attachment={attachment} attachment={attachment}
isDownloaded={isDownloaded} isDownloaded={isDownloaded}
softCorners curveBottomLeft={CurveType.Tiny}
curveBottomRight={CurveType.Tiny}
curveTopLeft={CurveType.Tiny}
curveTopRight={CurveType.Tiny}
playIconOverlay={isVideo} playIconOverlay={isVideo}
height={IMAGE_HEIGHT} height={IMAGE_HEIGHT}
width={IMAGE_WIDTH} width={IMAGE_WIDTH}

View File

@ -9,7 +9,7 @@ import { storiesOf } from '@storybook/react';
import { pngUrl } from '../../storybook/Fixtures'; import { pngUrl } from '../../storybook/Fixtures';
import type { Props } from './Image'; import type { Props } from './Image';
import { Image } from './Image'; import { CurveType, Image } from './Image';
import { IMAGE_PNG } from '../../types/MIME'; import { IMAGE_PNG } from '../../types/MIME';
import type { ThemeType } from '../../types/Util'; import type { ThemeType } from '../../types/Util';
import { setupI18n } from '../../util/setupI18n'; import { setupI18n } from '../../util/setupI18n';
@ -34,16 +34,22 @@ const createProps = (overrideProps: Partial<Props> = {}): Props => ({
blurHash: text('blurHash', overrideProps.blurHash || ''), blurHash: text('blurHash', overrideProps.blurHash || ''),
bottomOverlay: boolean('bottomOverlay', overrideProps.bottomOverlay || false), bottomOverlay: boolean('bottomOverlay', overrideProps.bottomOverlay || false),
closeButton: boolean('closeButton', overrideProps.closeButton || false), closeButton: boolean('closeButton', overrideProps.closeButton || false),
curveBottomLeft: boolean( curveBottomLeft: number(
'curveBottomLeft', 'curveBottomLeft',
overrideProps.curveBottomLeft || false overrideProps.curveBottomLeft || CurveType.None
), ),
curveBottomRight: boolean( curveBottomRight: number(
'curveBottomRight', 'curveBottomRight',
overrideProps.curveBottomRight || false overrideProps.curveBottomRight || CurveType.None
),
curveTopLeft: number(
'curveTopLeft',
overrideProps.curveTopLeft || CurveType.None
),
curveTopRight: number(
'curveTopRight',
overrideProps.curveTopRight || CurveType.None
), ),
curveTopLeft: boolean('curveTopLeft', overrideProps.curveTopLeft || false),
curveTopRight: boolean('curveTopRight', overrideProps.curveTopRight || false),
darkOverlay: boolean('darkOverlay', overrideProps.darkOverlay || false), darkOverlay: boolean('darkOverlay', overrideProps.darkOverlay || false),
height: number('height', overrideProps.height || 100), height: number('height', overrideProps.height || 100),
i18n, i18n,
@ -57,11 +63,6 @@ const createProps = (overrideProps: Partial<Props> = {}): Props => ({
'playIconOverlay', 'playIconOverlay',
overrideProps.playIconOverlay || false overrideProps.playIconOverlay || false
), ),
smallCurveTopLeft: boolean(
'smallCurveTopLeft',
overrideProps.smallCurveTopLeft || false
),
softCorners: boolean('softCorners', overrideProps.softCorners || false),
tabIndex: number('tabIndex', overrideProps.tabIndex || 0), tabIndex: number('tabIndex', overrideProps.tabIndex || 0),
theme: text('theme', overrideProps.theme || 'light') as ThemeType, theme: text('theme', overrideProps.theme || 'light') as ThemeType,
url: text('url', 'url' in overrideProps ? overrideProps.url || null : pngUrl), url: text('url', 'url' in overrideProps ? overrideProps.url || null : pngUrl),
@ -145,10 +146,10 @@ story.add('Pending w/blurhash', () => {
story.add('Curved Corners', () => { story.add('Curved Corners', () => {
const props = createProps({ const props = createProps({
curveBottomLeft: true, curveBottomLeft: CurveType.Normal,
curveBottomRight: true, curveBottomRight: CurveType.Normal,
curveTopLeft: true, curveTopLeft: CurveType.Normal,
curveTopRight: true, curveTopRight: CurveType.Normal,
}); });
return <Image {...props} />; return <Image {...props} />;
@ -156,7 +157,7 @@ story.add('Curved Corners', () => {
story.add('Small Curve Top Left', () => { story.add('Small Curve Top Left', () => {
const props = createProps({ const props = createProps({
smallCurveTopLeft: true, curveTopLeft: CurveType.Small,
}); });
return <Image {...props} />; return <Image {...props} />;
@ -164,7 +165,10 @@ story.add('Small Curve Top Left', () => {
story.add('Soft Corners', () => { story.add('Soft Corners', () => {
const props = createProps({ const props = createProps({
softCorners: true, curveBottomLeft: CurveType.Tiny,
curveBottomRight: CurveType.Tiny,
curveTopLeft: CurveType.Tiny,
curveTopRight: CurveType.Tiny,
}); });
return <Image {...props} />; return <Image {...props} />;

View File

@ -13,6 +13,13 @@ import {
defaultBlurHash, defaultBlurHash,
} from '../../types/Attachment'; } from '../../types/Attachment';
export enum CurveType {
None = 0,
Tiny = 4,
Small = 10,
Normal = 18,
}
export type Props = { export type Props = {
alt: string; alt: string;
attachment: AttachmentType; attachment: AttachmentType;
@ -32,16 +39,13 @@ export type Props = {
noBackground?: boolean; noBackground?: boolean;
bottomOverlay?: boolean; bottomOverlay?: boolean;
closeButton?: boolean; closeButton?: boolean;
curveBottomLeft?: boolean; curveBottomLeft?: CurveType;
curveBottomRight?: boolean; curveBottomRight?: CurveType;
curveTopLeft?: boolean; curveTopLeft?: CurveType;
curveTopRight?: boolean; curveTopRight?: CurveType;
smallCurveTopLeft?: boolean;
darkOverlay?: boolean; darkOverlay?: boolean;
playIconOverlay?: boolean; playIconOverlay?: boolean;
softCorners?: boolean;
blurHash?: string; blurHash?: string;
i18n: LocalizerType; i18n: LocalizerType;
@ -158,8 +162,6 @@ export class Image extends React.Component<Props> {
onError, onError,
overlayText, overlayText,
playIconOverlay, playIconOverlay,
smallCurveTopLeft,
softCorners,
tabIndex, tabIndex,
theme, theme,
url, url,
@ -176,25 +178,25 @@ export class Image extends React.Component<Props> {
const resolvedBlurHash = blurHash || defaultBlurHash(theme); const resolvedBlurHash = blurHash || defaultBlurHash(theme);
const overlayClassName = classNames('module-image__border-overlay', { const curveStyles = {
'module-image__border-overlay--with-border': !noBorder, borderTopLeftRadius: curveTopLeft || CurveType.None,
'module-image__border-overlay--with-click-handler': canClick, borderTopRightRadius: curveTopRight || CurveType.None,
'module-image--curved-top-left': curveTopLeft, borderBottomLeftRadius: curveBottomLeft || CurveType.None,
'module-image--curved-top-right': curveTopRight, borderBottomRightRadius: curveBottomRight || CurveType.None,
'module-image--curved-bottom-left': curveBottomLeft, };
'module-image--curved-bottom-right': curveBottomRight,
'module-image--small-curved-top-left': smallCurveTopLeft,
'module-image--soft-corners': softCorners,
'module-image__border-overlay--dark': darkOverlay,
'module-image--not-downloaded': imgNotDownloaded,
});
const overlay = canClick ? ( const overlay = canClick ? (
// Not sure what this button does. // Not sure what this button does.
// eslint-disable-next-line jsx-a11y/control-has-associated-label // eslint-disable-next-line jsx-a11y/control-has-associated-label
<button <button
type="button" type="button"
className={overlayClassName} className={classNames('module-image__border-overlay', {
'module-image__border-overlay--with-border': !noBorder,
'module-image__border-overlay--with-click-handler': canClick,
'module-image__border-overlay--dark': darkOverlay,
'module-image--not-downloaded': imgNotDownloaded,
})}
style={curveStyles}
onClick={this.handleClick} onClick={this.handleClick}
onKeyDown={this.handleKeyDown} onKeyDown={this.handleKeyDown}
tabIndex={tabIndex} tabIndex={tabIndex}
@ -210,15 +212,13 @@ export class Image extends React.Component<Props> {
'module-image', 'module-image',
className, className,
!noBackground ? 'module-image--with-background' : null, !noBackground ? 'module-image--with-background' : null,
curveBottomLeft ? 'module-image--curved-bottom-left' : null,
curveBottomRight ? 'module-image--curved-bottom-right' : null,
curveTopLeft ? 'module-image--curved-top-left' : null,
curveTopRight ? 'module-image--curved-top-right' : null,
smallCurveTopLeft ? 'module-image--small-curved-top-left' : null,
softCorners ? 'module-image--soft-corners' : null,
cropWidth || cropHeight ? 'module-image--cropped' : null cropWidth || cropHeight ? 'module-image--cropped' : null
)} )}
style={{ width: width - cropWidth, height: height - cropHeight }} style={{
width: width - cropWidth,
height: height - cropHeight,
...curveStyles,
}}
> >
{pending ? ( {pending ? (
this.renderPending() this.renderPending()
@ -248,11 +248,11 @@ export class Image extends React.Component<Props> {
) : null} ) : null}
{bottomOverlay ? ( {bottomOverlay ? (
<div <div
className={classNames( className="module-image__bottom-overlay"
'module-image__bottom-overlay', style={{
curveBottomLeft ? 'module-image--curved-bottom-left' : null, borderBottomLeftRadius: curveBottomLeft || CurveType.None,
curveBottomRight ? 'module-image--curved-bottom-right' : null borderBottomRightRadius: curveBottomRight || CurveType.None,
)} }}
/> />
) : null} ) : null}
{!pending && !imgNotDownloaded && playIconOverlay ? ( {!pending && !imgNotDownloaded && playIconOverlay ? (

View File

@ -37,6 +37,7 @@ const createProps = (overrideProps: Partial<Props> = {}): Props => ({
}), }),
], ],
bottomOverlay: boolean('bottomOverlay', overrideProps.bottomOverlay || false), bottomOverlay: boolean('bottomOverlay', overrideProps.bottomOverlay || false),
direction: overrideProps.direction || 'incoming',
i18n, i18n,
isSticker: boolean('isSticker', overrideProps.isSticker || false), isSticker: boolean('isSticker', overrideProps.isSticker || false),
onClick: action('onClick'), onClick: action('onClick'),

View File

@ -14,18 +14,23 @@ import {
isVideoAttachment, isVideoAttachment,
} from '../../types/Attachment'; } from '../../types/Attachment';
import { Image } from './Image'; import { Image, CurveType } from './Image';
import type { LocalizerType, ThemeType } from '../../types/Util'; import type { LocalizerType, ThemeType } from '../../types/Util';
export type DirectionType = 'incoming' | 'outgoing';
export type Props = { export type Props = {
attachments: Array<AttachmentType>; attachments: Array<AttachmentType>;
withContentAbove?: boolean;
withContentBelow?: boolean;
bottomOverlay?: boolean; bottomOverlay?: boolean;
direction: DirectionType;
isSticker?: boolean; isSticker?: boolean;
shouldCollapseAbove?: boolean;
shouldCollapseBelow?: boolean;
stickerSize?: number; stickerSize?: number;
tabIndex?: number; tabIndex?: number;
withContentAbove?: boolean;
withContentBelow?: boolean;
i18n: LocalizerType; i18n: LocalizerType;
theme?: ThemeType; theme?: ThemeType;
@ -36,27 +41,85 @@ export type Props = {
const GAP = 1; const GAP = 1;
function getCurves({
direction,
shouldCollapseAbove,
shouldCollapseBelow,
withContentAbove,
withContentBelow,
}: {
direction: DirectionType;
shouldCollapseAbove?: boolean;
shouldCollapseBelow?: boolean;
withContentAbove?: boolean;
withContentBelow?: boolean;
}): {
curveTopLeft: CurveType;
curveTopRight: CurveType;
curveBottomLeft: CurveType;
curveBottomRight: CurveType;
} {
let curveTopLeft = CurveType.None;
let curveTopRight = CurveType.None;
let curveBottomLeft = CurveType.None;
let curveBottomRight = CurveType.None;
if (shouldCollapseAbove && direction === 'incoming') {
curveTopLeft = CurveType.Tiny;
curveTopRight = CurveType.Normal;
} else if (shouldCollapseAbove && direction === 'outgoing') {
curveTopLeft = CurveType.Normal;
curveTopRight = CurveType.Tiny;
} else if (!withContentAbove) {
curveTopLeft = CurveType.Normal;
curveTopRight = CurveType.Normal;
}
if (shouldCollapseBelow && direction === 'incoming') {
curveBottomLeft = CurveType.Tiny;
curveBottomRight = CurveType.None;
} else if (shouldCollapseBelow && direction === 'outgoing') {
curveBottomLeft = CurveType.None;
curveBottomRight = CurveType.Tiny;
} else if (!withContentBelow) {
curveBottomLeft = CurveType.Normal;
curveBottomRight = CurveType.Normal;
}
return {
curveTopLeft,
curveTopRight,
curveBottomLeft,
curveBottomRight,
};
}
export const ImageGrid = ({ export const ImageGrid = ({
attachments, attachments,
bottomOverlay, bottomOverlay,
direction,
i18n, i18n,
isSticker, isSticker,
stickerSize, stickerSize,
onError, onError,
onClick, onClick,
shouldCollapseAbove,
shouldCollapseBelow,
tabIndex, tabIndex,
theme, theme,
withContentAbove, withContentAbove,
withContentBelow, withContentBelow,
}: Props): JSX.Element | null => { }: Props): JSX.Element | null => {
const curveTopLeft = !withContentAbove; const { curveTopLeft, curveTopRight, curveBottomLeft, curveBottomRight } =
const curveTopRight = curveTopLeft; getCurves({
direction,
shouldCollapseAbove,
shouldCollapseBelow,
withContentAbove,
withContentBelow,
});
const curveBottom = !withContentBelow; const withBottomOverlay = Boolean(bottomOverlay && !withContentBelow);
const curveBottomLeft = curveBottom;
const curveBottomRight = curveBottom;
const withBottomOverlay = Boolean(bottomOverlay && curveBottom);
if (!attachments || !attachments.length) { if (!attachments || !attachments.length) {
return null; return null;

View File

@ -222,15 +222,30 @@ const renderMany = (propsArray: ReadonlyArray<Props>) =>
/> />
)); ));
const renderBothDirections = (props: Props) => const renderThree = (props: Props) => renderMany([props, props, props]);
renderMany([
props, const renderBothDirections = (props: Props) => (
{ <>
{renderThree(props)}
{renderThree({
...props, ...props,
author: { ...props.author, id: getDefaultConversation().id }, author: { ...props.author, id: getDefaultConversation().id },
direction: 'outgoing', direction: 'outgoing',
}, })}
]); </>
);
const renderSingleBothDirections = (props: Props) => (
<>
<Message {...props} />
<Message
{...{
...props,
author: { ...props.author, id: getDefaultConversation().id },
direction: 'outgoing',
}}
/>
</>
);
story.add('Plain Message', () => { story.add('Plain Message', () => {
const props = createProps({ const props = createProps({
@ -353,7 +368,7 @@ story.add('Delivered', () => {
text: 'Hello there from a pal! I am sending a long message so that it will wrap a bit, since I like that look.', text: 'Hello there from a pal! I am sending a long message so that it will wrap a bit, since I like that look.',
}); });
return <Message {...props} />; return renderThree(props);
}); });
story.add('Read', () => { story.add('Read', () => {
@ -363,7 +378,7 @@ story.add('Read', () => {
text: 'Hello there from a pal! I am sending a long message so that it will wrap a bit, since I like that look.', text: 'Hello there from a pal! I am sending a long message so that it will wrap a bit, since I like that look.',
}); });
return <Message {...props} />; return renderThree(props);
}); });
story.add('Sending', () => { story.add('Sending', () => {
@ -373,7 +388,7 @@ story.add('Sending', () => {
text: 'Hello there from a pal! I am sending a long message so that it will wrap a bit, since I like that look.', text: 'Hello there from a pal! I am sending a long message so that it will wrap a bit, since I like that look.',
}); });
return <Message {...props} />; return renderThree(props);
}); });
story.add('Expiring', () => { story.add('Expiring', () => {
@ -502,7 +517,7 @@ story.add('Reactions (wider message)', () => {
], ],
}); });
return renderBothDirections(props); return renderSingleBothDirections(props);
}); });
const joyReactions = Array.from({ length: 52 }, () => getJoyReaction()); const joyReactions = Array.from({ length: 52 }, () => getJoyReaction());
@ -577,7 +592,7 @@ story.add('Reactions (short message)', () => {
], ],
}); });
return renderBothDirections(props); return renderSingleBothDirections(props);
}); });
story.add('Avatar in Group', () => { story.add('Avatar in Group', () => {
@ -588,7 +603,7 @@ story.add('Avatar in Group', () => {
text: 'Hello it is me, the saxophone.', text: 'Hello it is me, the saxophone.',
}); });
return <Message {...props} />; return renderThree(props);
}); });
story.add('Badge in Group', () => { story.add('Badge in Group', () => {
@ -599,7 +614,7 @@ story.add('Badge in Group', () => {
text: 'Hello it is me, the saxophone.', text: 'Hello it is me, the saxophone.',
}); });
return <Message {...props} />; return renderThree(props);
}); });
story.add('Sticker', () => { story.add('Sticker', () => {
@ -673,8 +688,8 @@ story.add('Deleted with error', () => {
return ( return (
<> <>
<Message {...propsPartialError} /> {renderThree(propsPartialError)}
<Message {...propsError} /> {renderThree(propsError)}
</> </>
); );
}); });
@ -684,9 +699,10 @@ story.add('Can delete for everyone', () => {
status: 'read', status: 'read',
text: 'I hope you get this.', text: 'I hope you get this.',
canDeleteForEveryone: true, canDeleteForEveryone: true,
direction: 'outgoing',
}); });
return <Message {...props} direction="outgoing" />; return renderThree(props);
}); });
story.add('Error', () => { story.add('Error', () => {
@ -916,7 +932,7 @@ story.add('Link Preview with too new a date', () => {
}); });
story.add('Image', () => { story.add('Image', () => {
const props = createProps({ const darkImageProps = createProps({
attachments: [ attachments: [
fakeAttachment({ fakeAttachment({
url: '/fixtures/tina-rolf-269345-unsplash.jpg', url: '/fixtures/tina-rolf-269345-unsplash.jpg',
@ -928,8 +944,25 @@ story.add('Image', () => {
], ],
status: 'sent', status: 'sent',
}); });
const lightImageProps = createProps({
attachments: [
fakeAttachment({
url: pngUrl,
fileName: 'the-sax.png',
contentType: IMAGE_PNG,
height: 240,
width: 320,
}),
],
status: 'sent',
});
return renderBothDirections(props); return (
<>
{renderBothDirections(darkImageProps)}
{renderBothDirections(lightImageProps)}
</>
);
}); });
for (let i = 2; i <= 5; i += 1) { for (let i = 2; i <= 5; i += 1) {
@ -937,39 +970,39 @@ for (let i = 2; i <= 5; i += 1) {
const props = createProps({ const props = createProps({
attachments: [ attachments: [
fakeAttachment({ fakeAttachment({
url: '/fixtures/tina-rolf-269345-unsplash.jpg', url: pngUrl,
fileName: 'tina-rolf-269345-unsplash.jpg', fileName: 'the-sax.png',
contentType: IMAGE_JPEG, contentType: IMAGE_PNG,
width: 128, height: 240,
height: 128, width: 320,
}), }),
fakeAttachment({ fakeAttachment({
url: '/fixtures/tina-rolf-269345-unsplash.jpg', url: pngUrl,
fileName: 'tina-rolf-269345-unsplash.jpg', fileName: 'the-sax.png',
contentType: IMAGE_JPEG, contentType: IMAGE_PNG,
width: 128, height: 240,
height: 128, width: 320,
}), }),
fakeAttachment({ fakeAttachment({
url: '/fixtures/tina-rolf-269345-unsplash.jpg', url: pngUrl,
fileName: 'tina-rolf-269345-unsplash.jpg', fileName: 'the-sax.png',
contentType: IMAGE_JPEG, contentType: IMAGE_PNG,
width: 128, height: 240,
height: 128, width: 320,
}), }),
fakeAttachment({ fakeAttachment({
url: '/fixtures/tina-rolf-269345-unsplash.jpg', url: pngUrl,
fileName: 'tina-rolf-269345-unsplash.jpg', fileName: 'the-sax.png',
contentType: IMAGE_JPEG, contentType: IMAGE_PNG,
width: 128, height: 240,
height: 128, width: 320,
}), }),
fakeAttachment({ fakeAttachment({
url: '/fixtures/tina-rolf-269345-unsplash.jpg', url: pngUrl,
fileName: 'tina-rolf-269345-unsplash.jpg', fileName: 'the-sax.png',
contentType: IMAGE_JPEG, contentType: IMAGE_PNG,
width: 128, height: 240,
height: 128, width: 320,
}), }),
].slice(0, i), ].slice(0, i),
status: 'sent', status: 'sent',
@ -1316,7 +1349,7 @@ story.add('TapToView Error', () => {
status: 'sent', status: 'sent',
}); });
return <Message {...props} />; return renderThree(props);
}); });
story.add('Dangerous File Type', () => { story.add('Dangerous File Type', () => {
@ -1419,23 +1452,23 @@ story.add('Not approved, with link preview', () => {
story.add('Custom Color', () => ( story.add('Custom Color', () => (
<> <>
<Message {renderThree({
{...createProps({ text: 'Solid.' })} ...createProps({ text: 'Solid.' }),
direction="outgoing" direction: 'outgoing',
customColor={{ customColor: {
start: { hue: 82, saturation: 35 }, start: { hue: 82, saturation: 35 },
}} },
/> })}
<br style={{ clear: 'both' }} /> <br style={{ clear: 'both' }} />
<Message {renderThree({
{...createProps({ text: 'Gradient.' })} ...createProps({ text: 'Gradient.' }),
direction="outgoing" direction: 'outgoing',
customColor={{ customColor: {
deg: 192, deg: 192,
start: { hue: 304, saturation: 85 }, start: { hue: 304, saturation: 85 },
end: { hue: 231, saturation: 76 }, end: { hue: 231, saturation: 76 },
}} },
/> })}
</> </>
)); ));
@ -1506,20 +1539,18 @@ story.add('Collapsing text-only group messages', () => {
story.add('Story reply', () => { story.add('Story reply', () => {
const conversation = getDefaultConversation(); const conversation = getDefaultConversation();
return ( return renderThree({
<Message ...createProps({ text: 'Wow!' }),
{...createProps({ text: 'Wow!' })} storyReplyContext: {
storyReplyContext={{ authorTitle: conversation.title,
authorTitle: conversation.title, conversationColor: ConversationColors[0],
conversationColor: ConversationColors[0], isFromMe: false,
isFromMe: false, rawAttachment: fakeAttachment({
rawAttachment: fakeAttachment({ url: '/fixtures/snow.jpg',
url: '/fixtures/snow.jpg', thumbnail: fakeThumbnail('/fixtures/snow.jpg'),
thumbnail: fakeThumbnail('/fixtures/snow.jpg'), }),
}), },
}} });
/>
);
}); });
const fullContact = { const fullContact = {
@ -1559,7 +1590,7 @@ story.add('EmbeddedContact: Full Contact', () => {
return renderBothDirections(props); return renderBothDirections(props);
}); });
story.add('EmbeddedContact: 2x Incoming, with Send Message', () => { story.add('EmbeddedContact: with Send Message', () => {
const props = createProps({ const props = createProps({
contact: { contact: {
...fullContact, ...fullContact,
@ -1568,19 +1599,7 @@ story.add('EmbeddedContact: 2x Incoming, with Send Message', () => {
}, },
direction: 'incoming', direction: 'incoming',
}); });
return renderMany([props, props]); return renderBothDirections(props);
});
story.add('EmbeddedContact: 2x Outgoing, with Send Message', () => {
const props = createProps({
contact: {
...fullContact,
firstNumber: fullContact.number[0].value,
uuid: UUID.generate().toString(),
},
direction: 'outgoing',
});
return renderMany([props, props]);
}); });
story.add('EmbeddedContact: Only Email', () => { story.add('EmbeddedContact: Only Email', () => {

View File

@ -28,7 +28,7 @@ import { MessageMetadata } from './MessageMetadata';
import { MessageTextMetadataSpacer } from './MessageTextMetadataSpacer'; import { MessageTextMetadataSpacer } from './MessageTextMetadataSpacer';
import { ImageGrid } from './ImageGrid'; import { ImageGrid } from './ImageGrid';
import { GIF } from './GIF'; import { GIF } from './GIF';
import { Image } from './Image'; import { CurveType, Image } from './Image';
import { ContactName } from './ContactName'; import { ContactName } from './ContactName';
import type { QuotedAttachmentType } from './Quote'; import type { QuotedAttachmentType } from './Quote';
import { Quote } from './Quote'; import { Quote } from './Quote';
@ -908,18 +908,17 @@ export class Message extends React.PureComponent<Props, State> {
<div className={containerClassName}> <div className={containerClassName}>
<ImageGrid <ImageGrid
attachments={attachments} attachments={attachments}
withContentAbove={ direction={direction}
isSticker || withContentAbove || shouldCollapseAbove withContentAbove={isSticker || withContentAbove}
} withContentBelow={isSticker || withContentBelow}
withContentBelow={
isSticker || withContentBelow || shouldCollapseBelow
}
isSticker={isSticker} isSticker={isSticker}
stickerSize={STICKER_SIZE} stickerSize={STICKER_SIZE}
bottomOverlay={bottomOverlay} bottomOverlay={bottomOverlay}
i18n={i18n} i18n={i18n}
theme={theme}
onError={this.handleImageError} onError={this.handleImageError}
theme={theme}
shouldCollapseAbove={shouldCollapseAbove}
shouldCollapseBelow={shouldCollapseBelow}
tabIndex={tabIndex} tabIndex={tabIndex}
onClick={attachment => { onClick={attachment => {
if (!isDownloaded(attachment)) { if (!isDownloaded(attachment)) {
@ -1060,6 +1059,7 @@ export class Message extends React.PureComponent<Props, State> {
openLink, openLink,
previews, previews,
quote, quote,
shouldCollapseAbove,
theme, theme,
kickOffAttachmentDownload, kickOffAttachmentDownload,
} = this.props; } = this.props;
@ -1113,6 +1113,8 @@ export class Message extends React.PureComponent<Props, State> {
<ImageGrid <ImageGrid
attachments={[first.image]} attachments={[first.image]}
withContentAbove={withContentAbove} withContentAbove={withContentAbove}
direction={direction}
shouldCollapseAbove={shouldCollapseAbove}
withContentBelow withContentBelow
onError={this.handleImageError} onError={this.handleImageError}
i18n={i18n} i18n={i18n}
@ -1124,10 +1126,14 @@ export class Message extends React.PureComponent<Props, State> {
{first.image && previewHasImage && !isFullSizeImage ? ( {first.image && previewHasImage && !isFullSizeImage ? (
<div className="module-message__link-preview__icon_container"> <div className="module-message__link-preview__icon_container">
<Image <Image
smallCurveTopLeft={!withContentAbove}
noBorder noBorder
noBackground noBackground
softCorners curveBottomLeft={
withContentAbove ? CurveType.Tiny : CurveType.Small
}
curveBottomRight={CurveType.Tiny}
curveTopRight={CurveType.Tiny}
curveTopLeft={CurveType.Tiny}
alt={i18n('previewThumbnail', [first.domain])} alt={i18n('previewThumbnail', [first.domain])}
height={72} height={72}
width={72} width={72}

View File

@ -5,7 +5,7 @@ import React from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import { unescape } from 'lodash'; import { unescape } from 'lodash';
import { Image } from './Image'; import { CurveType, Image } from './Image';
import { LinkPreviewDate } from './LinkPreviewDate'; import { LinkPreviewDate } from './LinkPreviewDate';
import type { AttachmentType } from '../../types/Attachment'; import type { AttachmentType } from '../../types/Attachment';
@ -51,7 +51,10 @@ export const StagedLinkPreview: React.FC<Props> = ({
<div className="module-staged-link-preview__icon-container"> <div className="module-staged-link-preview__icon-container">
<Image <Image
alt={i18n('stagedPreviewThumbnail', [domain])} alt={i18n('stagedPreviewThumbnail', [domain])}
softCorners curveBottomLeft={CurveType.Tiny}
curveBottomRight={CurveType.Tiny}
curveTopRight={CurveType.Tiny}
curveTopLeft={CurveType.Tiny}
height={72} height={72}
width={72} width={72}
url={image.url} url={image.url}