Keyboard/mouse mode and keyboard support bugfixes

This commit is contained in:
Scott Nonnenberg
2019-11-21 11:16:06 -08:00
committed by Ken Powers
parent ed55006f20
commit 2a0a73cfc1
25 changed files with 686 additions and 274 deletions

View File

@@ -22,7 +22,7 @@ const contact = {
onSendMessage: () => console.log('onSendMessage'),
signalAccount: '+12025550000',
};
<util.ConversationContext theme={util.theme} ios={util.ios}>
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
<div className="module-message-container">
<Message
authorColor="green"
@@ -92,7 +92,7 @@ const contact = {
onSendMessage: () => console.log('onSendMessage'),
signalAccount: '+12025550000',
};
<util.ConversationContext theme={util.theme} ios={util.ios}>
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
<div className="module-message-container">
<Message
authorColor="green"
@@ -137,7 +137,7 @@ const contact = {
},
},
};
<util.ConversationContext theme={util.theme} ios={util.ios}>
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
<div className="module-message-container">
<Message
authorColor="green"
@@ -182,7 +182,7 @@ const contact = {
},
signalAccount: '+12025550000',
};
<util.ConversationContext theme={util.theme} ios={util.ios}>
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
<div className="module-message-container">
<Message
authorColor="green"
@@ -244,7 +244,7 @@ const contact = {
},
},
};
<util.ConversationContext theme={util.theme} ios={util.ios}>
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
<div className="module-message-container">
<Message
authorColor="green"
@@ -309,7 +309,7 @@ const contact = {
},
},
};
<util.ConversationContext theme={util.theme} ios={util.ios}>
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
<div className="module-message-container">
<Message
authorColor="green"
@@ -377,7 +377,7 @@ const contact = {
},
signalAccount: '+12025551000',
};
<util.ConversationContext theme={util.theme} ios={util.ios}>
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
<div className="module-message-container">
<Message
authorColor="green"
@@ -439,7 +439,7 @@ const contact = {
},
],
};
<util.ConversationContext theme={util.theme} ios={util.ios}>
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
<div className="module-message-container">
<Message
authorColor="green"
@@ -491,7 +491,7 @@ const contact = {
```jsx
const contact = {};
<util.ConversationContext theme={util.theme} ios={util.ios}>
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
<div className="module-message-container">
<Message
authorColor="green"
@@ -575,7 +575,7 @@ const contactWithoutAccount = {
},
},
};
<util.ConversationContext theme={util.theme} ios={util.ios}>
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
<div className="module-message-container">
<Message
text="I want to introduce you to Someone..."

View File

@@ -38,6 +38,7 @@ export class EmbeddedContact extends React.Component<Props> {
<button
className={classNames(
'module-embedded-contact',
`module-embedded-contact--${direction}`,
withContentAbove
? 'module-embedded-contact--with-content-above'
: null,

View File

@@ -4,7 +4,7 @@ export type PropsType = {
id: string;
conversationId: string;
isSelected: boolean;
selectMessage: (messageId: string, conversationId: string) => unknown;
selectMessage?: (messageId: string, conversationId: string) => unknown;
};
export class InlineNotificationWrapper extends React.Component<PropsType> {
@@ -18,10 +18,19 @@ export class InlineNotificationWrapper extends React.Component<PropsType> {
}
};
public handleFocus = () => {
// @ts-ignore
if (window.getInteractionMode() === 'keyboard') {
this.setSelected();
}
};
public setSelected = () => {
const { id, conversationId, selectMessage } = this.props;
selectMessage(id, conversationId);
if (selectMessage) {
selectMessage(id, conversationId);
}
};
public componentDidMount() {
@@ -45,7 +54,7 @@ export class InlineNotificationWrapper extends React.Component<PropsType> {
className="module-inline-notification-wrapper"
tabIndex={0}
ref={this.focusRef}
onFocus={this.setSelected}
onFocus={this.handleFocus}
>
{children}
</div>

View File

@@ -3,7 +3,7 @@
Note that timestamp and status can be hidden with the `collapseMetadata` boolean property.
```jsx
<util.ConversationContext theme={util.theme} ios={util.ios}>
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
<div className="module-message-container">
<Message
direction="incoming"
@@ -148,7 +148,7 @@ Note that timestamp and status can be hidden with the `collapseMetadata` boolean
### Status
```jsx
<util.ConversationContext theme={util.theme} ios={util.ios}>
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
<div className="module-message-container">
<Message
direction="outgoing"
@@ -323,7 +323,7 @@ Note that timestamp and status can be hidden with the `collapseMetadata` boolean
### All colors
```jsx
<util.ConversationContext theme={util.theme} ios={util.ios}>
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
<div className="module-message-container">
<Message
direction="incoming"
@@ -450,7 +450,7 @@ Note that timestamp and status can be hidden with the `collapseMetadata` boolean
### Long data
```jsx
<util.ConversationContext theme={util.theme} ios={util.ios}>
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
<div className="module-message-container">
<Message
authorColor="purple"
@@ -515,7 +515,7 @@ Note that timestamp and status can be hidden with the `collapseMetadata` boolean
### Pending long message download
```jsx
<util.ConversationContext theme={util.theme} ios={util.ios}>
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
<div className="module-message-container">
<Message
authorColor="purple"
@@ -553,7 +553,7 @@ Note that timestamp and status can be hidden with the `collapseMetadata` boolean
#### Image with caption
```jsx
<util.ConversationContext theme={util.theme} ios={util.ios}>
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
<div className="module-message-container">
<Message
authorColor="blue"
@@ -645,7 +645,7 @@ Note that timestamp and status can be hidden with the `collapseMetadata` boolean
First, showing the metadata overlay on dark and light images, then a message with `collapseMetadata` set.
```jsx
<util.ConversationContext theme={util.theme} ios={util.ios}>
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
<div className="module-message-container">
<Message
authorColor="green"
@@ -765,7 +765,7 @@ First, showing the metadata overlay on dark and light images, then a message wit
Stickers have no background, but they have all the standard message bubble features.
```jsx
<util.ConversationContext theme={util.theme} ios={util.ios}>
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
<div className="module-message-container">
<Message
authorColor="green"
@@ -912,7 +912,7 @@ Stickers have no background, but they have all the standard message bubble featu
First set is in a 1:1 conversation, second set is in a group.
```jsx
<util.ConversationContext theme={util.theme} ios={util.ios}>
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
<div className="module-message-container">
<Message
authorColor="green"
@@ -1002,7 +1002,7 @@ First set is in a 1:1 conversation, second set is in a group.
A sticker with no attachments (what our selectors produce for a pending sticker) is not displayed at all.
```jsx
<util.ConversationContext theme={util.theme} ios={util.ios}>
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
<div className="module-message-container">
<Message
authorColor="green"
@@ -1062,7 +1062,7 @@ A sticker with no attachments (what our selectors produce for a pending sticker)
#### Multiple images
```jsx
<util.ConversationContext theme={util.theme} ios={util.ios}>
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
<div className="module-message-container">
<Message
authorColor="green"
@@ -1244,7 +1244,7 @@ A sticker with no attachments (what our selectors produce for a pending sticker)
#### Multiple images with caption
```jsx
<util.ConversationContext theme={util.theme} ios={util.ios}>
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
<div className="module-message-container">
<Message
authorColor="green"
@@ -1433,7 +1433,7 @@ A sticker with no attachments (what our selectors produce for a pending sticker)
Note that the delivered indicator is always Signal Blue, not the conversation color.
```jsx
<util.ConversationContext theme={util.theme} ios={util.ios}>
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
<div className="module-message-container">
<Message
direction="outgoing"
@@ -1512,7 +1512,7 @@ Note that the delivered indicator is always Signal Blue, not the conversation co
#### Pending images
```
<util.ConversationContext theme={util.theme} ios={util.ios}>
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
<div className="module-message-container">
<Message
authorColor="green"
@@ -1617,7 +1617,7 @@ Note that the delivered indicator is always Signal Blue, not the conversation co
#### Image with portrait aspect ratio
```jsx
<util.ConversationContext theme={util.theme} ios={util.ios}>
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
<div className="module-message-container">
<Message
authorColor="purple"
@@ -1695,7 +1695,7 @@ Note that the delivered indicator is always Signal Blue, not the conversation co
#### Image with portrait aspect ratio and caption
```jsx
<util.ConversationContext theme={util.theme} ios={util.ios}>
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
<div className="module-message-container">
<Message
authorColor="green"
@@ -1819,7 +1819,7 @@ Note that the delivered indicator is always Signal Blue, not the conversation co
#### Image with landscape aspect ratio
```jsx
<util.ConversationContext theme={util.theme} ios={util.ios}>
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
<div className="module-message-container">
<Message
authorColor="green"
@@ -1897,7 +1897,7 @@ Note that the delivered indicator is always Signal Blue, not the conversation co
#### Image with landscape aspect ratio and caption
```jsx
<util.ConversationContext theme={util.theme} ios={util.ios}>
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
<div className="module-message-container">
<Message
authorColor="green"
@@ -1979,7 +1979,7 @@ Note that the delivered indicator is always Signal Blue, not the conversation co
#### Video with caption
```jsx
<util.ConversationContext theme={util.theme} ios={util.ios}>
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
<div className="module-message-container">
<Message
authorColor="green"
@@ -2073,7 +2073,7 @@ Note that the delivered indicator is always Signal Blue, not the conversation co
#### Video
```jsx
<util.ConversationContext theme={util.theme} ios={util.ios}>
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
<div className="module-message-container">
<Message
authorColor="green"
@@ -2170,7 +2170,7 @@ Note that the delivered indicator is always Signal Blue, not the conversation co
#### Missing images and videos
```jsx
<util.ConversationContext theme={util.theme} ios={util.ios}>
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
<div className="module-message-container">
<Message
authorColor="green"
@@ -2349,7 +2349,7 @@ Note that the delivered indicator is always Signal Blue, not the conversation co
#### Broken source URL images and videos
```jsx
<util.ConversationContext theme={util.theme} ios={util.ios}>
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
<div className="module-message-container">
<Message
authorColor="green"
@@ -2441,7 +2441,7 @@ Note that the delivered indicator is always Signal Blue, not the conversation co
#### Image/video which is too big
```jsx
<util.ConversationContext theme={util.theme} ios={util.ios}>
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
<div className="module-message-container">
<Message
authorColor="green"
@@ -2532,7 +2532,7 @@ Note that the delivered indicator is always Signal Blue, not the conversation co
#### Image/video missing height/width
```jsx
<util.ConversationContext theme={util.theme} ios={util.ios}>
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
<div className="module-message-container">
<Message
authorColor="green"
@@ -2619,7 +2619,7 @@ Note that the delivered indicator is always Signal Blue, not the conversation co
#### Audio with caption
```jsx
<util.ConversationContext theme={util.theme} ios={util.ios}>
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
<div className="module-message-container">
<Message
authorColor="green"
@@ -2693,7 +2693,7 @@ Note that the delivered indicator is always Signal Blue, not the conversation co
#### Audio
```jsx
<util.ConversationContext theme={util.theme} ios={util.ios}>
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
<div className="module-message-container">
<Message
authorColor="green"
@@ -2767,7 +2767,7 @@ Voice notes are not shown any differently from audio attachments.
#### Other file type with caption
```jsx
<util.ConversationContext theme={util.theme} ios={util.ios}>
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
<div className="module-message-container">
<Message
authorColor="green"
@@ -2906,7 +2906,7 @@ Voice notes are not shown any differently from audio attachments.
#### Other file type
```jsx
<util.ConversationContext theme={util.theme} ios={util.ios}>
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
<div className="module-message-container">
<Message
authorColor="green"
@@ -2984,7 +2984,7 @@ Voice notes are not shown any differently from audio attachments.
#### Other file type pending
```jsx
<util.ConversationContext theme={util.theme} ios={util.ios}>
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
<div className="module-message-container">
<Message
authorColor="green"
@@ -3062,7 +3062,7 @@ Voice notes are not shown any differently from audio attachments.
#### Dangerous file type
```jsx
<util.ConversationContext theme={util.theme} ios={util.ios}>
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
<div className="module-message-container">
<Message
authorColor="green"
@@ -3108,7 +3108,7 @@ Voice notes are not shown any differently from audio attachments.
#### Link previews, full-size image
```jsx
<util.ConversationContext theme={util.theme} ios={util.ios}>
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
<div className="module-message-container">
<Message
authorColor="green"
@@ -3219,7 +3219,7 @@ Voice notes are not shown any differently from audio attachments.
Sticker link previews are forced to use the small link preview form, no matter the image size.
```jsx
<util.ConversationContext theme={util.theme} ios={util.ios}>
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
<div className="module-message-container">
<Message
authorColor="green"
@@ -3273,7 +3273,7 @@ Sticker link previews are forced to use the small link preview form, no matter t
#### Link previews, small image
```jsx
<util.ConversationContext theme={util.theme} ios={util.ios}>
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
<div className="module-message-container">
<Message
authorColor="green"
@@ -3384,7 +3384,7 @@ Sticker link previews are forced to use the small link preview form, no matter t
#### Link previews with pending image
```jsx
<util.ConversationContext theme={util.theme} ios={util.ios}>
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
<div className="module-message-container">
<Message
authorColor="green"
@@ -3481,7 +3481,7 @@ Sticker link previews are forced to use the small link preview form, no matter t
#### Link previews, no image
```jsx
<util.ConversationContext theme={util.theme} ios={util.ios}>
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
<div className="module-message-container">
<Message
authorColor="green"
@@ -3568,7 +3568,7 @@ Sticker link previews are forced to use the small link preview form, no matter t
### Tap to view
```jsx
<util.ConversationContext theme={util.theme} ios={util.ios}>
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
<div className="module-message-container">
<Message
direction="incoming"
@@ -4004,7 +4004,7 @@ Sticker link previews are forced to use the small link preview form, no matter t
Note that the author avatar goes away if `collapseMetadata` is set.
```jsx
<util.ConversationContext theme={util.theme} ios={util.ios}>
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
<div className="module-message-container">
<Message
direction="incoming"

View File

@@ -40,6 +40,7 @@ interface Trigger {
// Same as MIN_WIDTH in ImageGrid.tsx
const MINIMUM_LINK_PREVIEW_IMAGE_WIDTH = 200;
const STICKER_SIZE = 128;
const SELECTED_TIMEOUT = 1000;
interface LinkPreviewType {
title: string;
@@ -56,6 +57,8 @@ export type PropsData = {
textPending?: boolean;
isSticker: boolean;
isSelected: boolean;
isSelectedCounter: number;
interactionMode: 'mouse' | 'keyboard';
direction: 'incoming' | 'outgoing';
timestamp: number;
status?: 'sending' | 'sent' | 'delivered' | 'read' | 'error';
@@ -130,7 +133,7 @@ export type PropsActions = {
sentAt: number;
}
) => void;
selectMessage: (messageId: string, conversationId: string) => unknown;
selectMessage?: (messageId: string, conversationId: string) => unknown;
};
export type Props = PropsData & PropsHousekeeping & PropsActions;
@@ -139,6 +142,9 @@ interface State {
expiring: boolean;
expired: boolean;
imageBroken: boolean;
isSelected: boolean;
prevSelectedCounter: number;
}
const EXPIRATION_CHECK_MINIMUM = 2000;
@@ -149,16 +155,46 @@ export class Message extends React.PureComponent<Props, State> {
public focusRef: React.RefObject<HTMLDivElement> = React.createRef();
public audioRef: React.RefObject<HTMLAudioElement> = React.createRef();
public state = {
expiring: false,
expired: false,
imageBroken: false,
};
public expirationCheckInterval: any;
public expiredTimeout: any;
public selectedTimeout: any;
public constructor(props: Props) {
super(props);
this.state = {
expiring: false,
expired: false,
imageBroken: false,
isSelected: props.isSelected,
prevSelectedCounter: props.isSelectedCounter,
};
}
public static getDerivedStateFromProps(props: Props, state: State): State {
if (!props.isSelected) {
return {
...state,
isSelected: false,
prevSelectedCounter: 0,
};
}
if (
props.isSelected &&
props.isSelectedCounter !== state.prevSelectedCounter
) {
return {
...state,
isSelected: props.isSelected,
prevSelectedCounter: props.isSelectedCounter,
};
}
return state;
}
public captureMenuTrigger = (triggerRef: Trigger) => {
this.menuTriggerRef = triggerRef;
};
@@ -180,10 +216,20 @@ export class Message extends React.PureComponent<Props, State> {
});
};
public handleFocus = () => {
const { interactionMode } = this.props;
if (interactionMode === 'keyboard') {
this.setSelected();
}
};
public setSelected = () => {
const { id, conversationId, selectMessage } = this.props;
selectMessage(id, conversationId);
if (selectMessage) {
selectMessage(id, conversationId);
}
};
public setFocus = () => {
@@ -195,6 +241,8 @@ export class Message extends React.PureComponent<Props, State> {
};
public componentDidMount() {
this.startSelectedTimer();
const { isSelected } = this.props;
if (isSelected) {
this.setFocus();
@@ -228,6 +276,8 @@ export class Message extends React.PureComponent<Props, State> {
}
public componentDidUpdate(prevProps: Props) {
this.startSelectedTimer();
if (!prevProps.isSelected && this.props.isSelected) {
this.setFocus();
}
@@ -235,6 +285,23 @@ export class Message extends React.PureComponent<Props, State> {
this.checkExpired();
}
public startSelectedTimer() {
const { interactionMode } = this.props;
const { isSelected } = this.state;
if (interactionMode === 'keyboard' || !isSelected) {
return;
}
if (!this.selectedTimeout) {
this.selectedTimeout = setTimeout(() => {
this.selectedTimeout = undefined;
this.setState({ isSelected: false });
this.props.clearSelectedMessage();
}, SELECTED_TIMEOUT);
}
}
public checkExpired() {
const now = Date.now();
const { isExpired, expirationTimestamp, expirationLength } = this.props;
@@ -598,6 +665,7 @@ export class Message extends React.PureComponent<Props, State> {
<button
className={classNames(
'module-message__link-preview',
`module-message__link-preview--${direction}`,
withContentAbove
? 'module-message__link-preview--with-content-above'
: null
@@ -1389,12 +1457,12 @@ export class Message extends React.PureComponent<Props, State> {
const {
authorColor,
direction,
isSelected,
isSticker,
isTapToView,
isTapToViewExpired,
isTapToViewError,
} = this.props;
const { isSelected } = this.state;
const isAttachmentPending = this.isAttachmentPending();
@@ -1447,7 +1515,7 @@ export class Message extends React.PureComponent<Props, State> {
isSticker,
timestamp,
} = this.props;
const { expired, expiring, imageBroken } = this.state;
const { expired, expiring, imageBroken, isSelected } = this.state;
// This id is what connects our triple-dot click with our associated pop-up menu.
// It needs to be unique.
@@ -1466,6 +1534,7 @@ export class Message extends React.PureComponent<Props, State> {
className={classNames(
'module-message',
`module-message--${direction}`,
isSelected ? 'module-message--selected' : null,
expiring ? 'module-message--expired' : null,
conversationType === 'group' ? 'module-message--group' : null
)}
@@ -1475,7 +1544,7 @@ export class Message extends React.PureComponent<Props, State> {
role="button"
onKeyDown={this.handleKeyDown}
onClick={this.handleClick}
onFocus={this.setSelected}
onFocus={this.handleFocus}
ref={this.focusRef}
>
{this.renderError(direction === 'incoming')}

View File

@@ -35,12 +35,7 @@ interface Props {
}
export class MessageDetail extends React.Component<Props> {
private readonly focusRef: React.RefObject<HTMLDivElement>;
constructor(props: Props) {
super(props);
this.focusRef = React.createRef();
}
private readonly focusRef = React.createRef<HTMLDivElement>();
public componentDidMount() {
// When this component is created, it's initially not part of the DOM, and then it's

View File

@@ -3,7 +3,7 @@
#### Plain text
```jsx
<util.ConversationContext theme={util.theme} ios={util.ios}>
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
<div className="module-message-container">
<Message
direction="incoming"
@@ -41,7 +41,7 @@
#### Name variations
```jsx
<util.ConversationContext theme={util.theme} ios={util.ios}>
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
<div className="module-message-container">
<Message
direction="incoming"
@@ -112,7 +112,7 @@
#### With emoji
```jsx
<util.ConversationContext theme={util.theme} ios={util.ios}>
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
<div className="module-message-container">
<Message
direction="incoming"
@@ -148,7 +148,7 @@
#### Replies to you or yourself
```jsx
<util.ConversationContext theme={util.theme} ios={util.ios}>
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
<div className="module-message-container">
<Message
direction="incoming"
@@ -186,7 +186,12 @@
#### In a group conversation
```jsx
<util.ConversationContext theme={util.theme} type="group" ios={util.ios}>
<util.ConversationContext
theme={util.theme}
type="group"
ios={util.ios}
mode={util.mode}
>
<div className="module-message-container">
<Message
direction="incoming"
@@ -231,7 +236,7 @@ Note: for incoming messages, quote color is taken from the parent message. For o
messages the color is taken from the contact who wrote the quoted message.
```jsx
<util.ConversationContext theme={util.theme} ios={util.ios}>
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
<div className="module-message-container">
<Message
direction="incoming"
@@ -610,7 +615,7 @@ messages the color is taken from the contact who wrote the quoted message.
#### Referenced message not found
```jsx
<util.ConversationContext theme={util.theme} ios={util.ios}>
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
<div className="module-message-container">
<Message
direction="incoming"
@@ -687,7 +692,7 @@ messages the color is taken from the contact who wrote the quoted message.
#### Long names and context
```jsx
<util.ConversationContext theme={util.theme} ios={util.ios}>
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
<div className="module-message-container">
<Message
direction="incoming"
@@ -729,7 +734,7 @@ messages the color is taken from the contact who wrote the quoted message.
#### A lot of text in quotation
```jsx
<util.ConversationContext theme={util.theme} ios={util.ios}>
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
<div className="module-message-container">
<Message
direction="incoming"
@@ -773,7 +778,7 @@ messages the color is taken from the contact who wrote the quoted message.
#### A lot of text in quotation, with icon
```jsx
<util.ConversationContext theme={util.theme} ios={util.ios}>
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
<div className="module-message-container">
<Message
direction="incoming"
@@ -825,7 +830,7 @@ messages the color is taken from the contact who wrote the quoted message.
#### A lot of text in quotation, with image
```jsx
<util.ConversationContext theme={util.theme} ios={util.ios}>
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
<div className="module-message-container">
<Message
direction="incoming"
@@ -885,7 +890,7 @@ messages the color is taken from the contact who wrote the quoted message.
#### Image with caption
```jsx
<util.ConversationContext theme={util.theme} ios={util.ios}>
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
<div className="module-message-container">
<Message
direction="incoming"
@@ -937,7 +942,7 @@ messages the color is taken from the contact who wrote the quoted message.
#### Image
```jsx
<util.ConversationContext theme={util.theme} ios={util.ios}>
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
<div className="module-message-container">
<Message
direction="incoming"
@@ -987,7 +992,7 @@ messages the color is taken from the contact who wrote the quoted message.
#### Image with no thumbnail
```jsx
<util.ConversationContext theme={util.theme} ios={util.ios}>
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
<div className="module-message-container">
<Message
direction="incoming"
@@ -1030,7 +1035,7 @@ messages the color is taken from the contact who wrote the quoted message.
#### Pending image download
```jsx
<util.ConversationContext theme={util.theme} ios={util.ios}>
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
<div className="module-message-container">
<Message
direction="incoming"
@@ -1073,7 +1078,7 @@ messages the color is taken from the contact who wrote the quoted message.
#### Video with caption
```jsx
<util.ConversationContext theme={util.theme} ios={util.ios}>
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
<div className="module-message-container">
<Message
direction="incoming"
@@ -1125,7 +1130,7 @@ messages the color is taken from the contact who wrote the quoted message.
#### Video
```jsx
<util.ConversationContext theme={util.theme} ios={util.ios}>
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
<div className="module-message-container">
<Message
direction="incoming"
@@ -1175,7 +1180,7 @@ messages the color is taken from the contact who wrote the quoted message.
#### Video with no thumbnail
```jsx
<util.ConversationContext theme={util.theme} ios={util.ios}>
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
<div className="module-message-container">
<Message
direction="incoming"
@@ -1223,7 +1228,7 @@ messages the color is taken from the contact who wrote the quoted message.
#### Audio with caption
```jsx
<util.ConversationContext theme={util.theme} ios={util.ios}>
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
<div className="module-message-container">
<Message
direction="incoming"
@@ -1267,7 +1272,7 @@ messages the color is taken from the contact who wrote the quoted message.
#### Audio
```jsx
<util.ConversationContext theme={util.theme} ios={util.ios}>
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
<div className="module-message-container">
<Message
direction="incoming"
@@ -1309,7 +1314,7 @@ messages the color is taken from the contact who wrote the quoted message.
#### Voice message
```jsx
<util.ConversationContext theme={util.theme} ios={util.ios}>
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
<div className="module-message-container">
<Message
direction="incoming"
@@ -1355,7 +1360,7 @@ messages the color is taken from the contact who wrote the quoted message.
#### Other file type with caption
```jsx
<util.ConversationContext theme={util.theme} ios={util.ios}>
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
<div className="module-message-container">
<Message
direction="incoming"
@@ -1438,7 +1443,7 @@ messages the color is taken from the contact who wrote the quoted message.
#### Other file type
```jsx
<util.ConversationContext theme={util.theme} ios={util.ios}>
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
<div className="module-message-container">
<Message
direction="incoming"
@@ -1482,7 +1487,7 @@ messages the color is taken from the contact who wrote the quoted message.
#### Quote, image attachment, and caption
```jsx
<util.ConversationContext theme={util.theme} ios={util.ios}>
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
<div className="module-message-container">
<Message
direction="incoming"
@@ -1532,7 +1537,7 @@ messages the color is taken from the contact who wrote the quoted message.
#### Quote, image attachment
```jsx
<util.ConversationContext theme={util.theme} ios={util.ios}>
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
<div className="module-message-container">
<Message
direction="incoming"
@@ -1580,7 +1585,7 @@ messages the color is taken from the contact who wrote the quoted message.
#### Quote, portrait image attachment
```jsx
<util.ConversationContext theme={util.theme} ios={util.ios}>
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
<div className="module-message-container">
<Message
direction="incoming"
@@ -1628,7 +1633,7 @@ messages the color is taken from the contact who wrote the quoted message.
#### Quote, video attachment
```jsx
<util.ConversationContext theme={util.theme} ios={util.ios}>
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
<div className="module-message-container">
<Message
direction="incoming"
@@ -1686,7 +1691,7 @@ messages the color is taken from the contact who wrote the quoted message.
#### Quote, audio attachment
```jsx
<util.ConversationContext theme={util.theme} ios={util.ios}>
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
<div className="module-message-container">
<Message
direction="incoming"
@@ -1730,7 +1735,7 @@ messages the color is taken from the contact who wrote the quoted message.
#### Quote, file attachment
```jsx
<util.ConversationContext theme={util.theme} ios={util.ios}>
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
<div className="module-message-container">
<Message
direction="incoming"
@@ -1778,7 +1783,7 @@ messages the color is taken from the contact who wrote the quoted message.
#### Plain text
```jsx
<util.ConversationContext theme={util.theme} ios={util.ios}>
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
<div className="bottom-bar">
<Quote
text="How many ferrets do you have?"
@@ -1796,7 +1801,7 @@ messages the color is taken from the contact who wrote the quoted message.
#### With an icon
```jsx
<util.ConversationContext theme={util.theme} ios={util.ios}>
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
<div className="bottom-bar">
<Quote
text="How many ferrets do you have?"
@@ -1818,7 +1823,7 @@ messages the color is taken from the contact who wrote the quoted message.
#### With an image
```jsx
<util.ConversationContext theme={util.theme} ios={util.ios}>
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
<div className="bottom-bar">
<Quote
text="How many ferrets do you have?"
@@ -1843,7 +1848,7 @@ messages the color is taken from the contact who wrote the quoted message.
#### With attachment and no text
```jsx
<util.ConversationContext theme={util.theme} ios={util.ios}>
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
<div className="bottom-bar">
<Quote
authorColor="blue"
@@ -1867,7 +1872,7 @@ messages the color is taken from the contact who wrote the quoted message.
#### With generic attachment
```jsx
<util.ConversationContext theme={util.theme} ios={util.ios}>
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
<div className="bottom-bar">
<Quote
text="How many ferrets do you have?"
@@ -1889,7 +1894,7 @@ messages the color is taken from the contact who wrote the quoted message.
#### With a close button
```jsx
<util.ConversationContext theme={util.theme} ios={util.ios}>
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
<div className="bottom-bar">
<Quote
text="How many ferrets do you have?"
@@ -1908,7 +1913,7 @@ messages the color is taken from the contact who wrote the quoted message.
#### With a close button and icon
```jsx
<util.ConversationContext theme={util.theme} ios={util.ios}>
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
<div className="bottom-bar">
<Quote
text="How many ferrets do you have?"
@@ -1931,7 +1936,7 @@ messages the color is taken from the contact who wrote the quoted message.
#### With a close button and image
```jsx
<util.ConversationContext theme={util.theme} ios={util.ios}>
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
<div className="bottom-bar">
<Quote
text="How many ferrets do you have?"

View File

@@ -99,20 +99,13 @@ export class Quote extends React.Component<Props, State> {
// This is important to ensure that using this quote to navigate to the referenced
// message doesn't also trigger its parent message's keydown.
if (onClick && (event.key === 'Enter' || event.key === 'Space')) {
if (onClick && (event.key === 'Enter' || event.key === ' ')) {
event.preventDefault();
event.stopPropagation();
onClick();
}
};
// We prevent this from bubbling to prevent the focus flash around a message when
// you click a quote.
public handleMouseDown = (event: React.MouseEvent) => {
event.preventDefault();
event.stopPropagation();
};
public handleImageError = () => {
// tslint:disable-next-line no-console
console.log('Message: Image failed to load; failing over to placeholder');
@@ -271,14 +264,20 @@ export class Quote extends React.Component<Props, State> {
return null;
}
// We don't want the overall click handler for the quote to fire, so we stop
// propagation before handing control to the caller's callback.
const onClick = (e: React.MouseEvent<{}>): void => {
const clickHandler = (e: React.MouseEvent): void => {
e.stopPropagation();
e.preventDefault();
onClose();
};
const keyDownHandler = (e: React.KeyboardEvent): void => {
if (e.key === 'Enter' || e.key === ' ') {
e.stopPropagation();
e.preventDefault();
onClose();
}
};
// We need the container to give us the flexibility to implement the iOS design.
return (
@@ -288,7 +287,8 @@ export class Quote extends React.Component<Props, State> {
// We can't be a button because the overall quote is a button; can't nest them
role="button"
className="module-quote__close-button"
onClick={onClick}
onKeyDown={keyDownHandler}
onClick={clickHandler}
/>
</div>
);
@@ -383,7 +383,6 @@ export class Quote extends React.Component<Props, State> {
<button
onClick={onClick}
onKeyDown={this.handleKeyDown}
onMouseDown={this.handleMouseDown}
className={classNames(
'module-quote',
isIncoming ? 'module-quote--incoming' : 'module-quote--outgoing',

View File

@@ -55,6 +55,7 @@ const Tab = ({
)}
onClick={handleClick}
role="tab"
tabIndex={0}
>
{label}
</div>
@@ -81,7 +82,7 @@ export class MediaGallery extends React.Component<Props, State> {
const { selectedTab } = this.state;
return (
<div className="module-media-gallery" tabIndex={0} ref={this.focusRef}>
<div className="module-media-gallery" tabIndex={-1} ref={this.focusRef}>
<div className="module-media-gallery__tab-container">
<Tab
label="Media"