Fix collapsed corners for link previews and image attachments
Co-authored-by: Scott Nonnenberg <scott@signal.org>
This commit is contained in:
parent
050a1bf789
commit
653e885266
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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} />;
|
||||||
|
|
|
@ -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 ? (
|
||||||
|
|
|
@ -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'),
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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', () => {
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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}
|
||||||
|
|
Loading…
Reference in New Issue