Adds keyboard affordance to story viewer

This commit is contained in:
Josh Perez 2022-04-07 17:11:33 -04:00 committed by GitHub
parent 09fbfe5421
commit 19bb3bc994
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 224 additions and 196 deletions

View file

@ -1,6 +1,7 @@
// Copyright 2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import FocusTrap from 'focus-trap-react';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { useSpring, animated, to } from '@react-spring/web';
import type { BodyRangeType, LocalizerType } from '../types/Util';
@ -197,170 +198,175 @@ export const StoryViewer = ({
}, [navigateStories]);
return (
<div className="StoryViewer">
<div className="StoryViewer__overlay" />
<div className="StoryViewer__content">
<button
aria-label={i18n('MyStories__more')}
className="StoryViewer__more"
type="button"
/>
<button
aria-label={i18n('close')}
className="StoryViewer__close-button"
onClick={onClose}
type="button"
/>
<div className="StoryViewer__container">
<StoryImage
attachment={attachment}
i18n={i18n}
label={i18n('lightboxImageAlt')}
moduleClassName="StoryViewer__story"
queueStoryDownload={queueStoryDownload}
storyId={messageId}
<FocusTrap>
<div className="StoryViewer">
<div className="StoryViewer__overlay" />
<div className="StoryViewer__content">
<button
aria-label={i18n('MyStories__more')}
className="StoryViewer__more"
tabIndex={0}
type="button"
/>
<div className="StoryViewer__meta">
<Avatar
acceptedMessageRequest={acceptedMessageRequest}
avatarPath={avatarPath}
badge={undefined}
color={getAvatarColor(color)}
conversationType="direct"
<button
aria-label={i18n('close')}
className="StoryViewer__close-button"
onClick={onClose}
tabIndex={0}
type="button"
/>
<div className="StoryViewer__container">
<StoryImage
attachment={attachment}
i18n={i18n}
isMe={Boolean(isMe)}
name={name}
profileName={profileName}
sharedGroupNames={sharedGroupNames}
size={AvatarSize.TWENTY_EIGHT}
title={title}
label={i18n('lightboxImageAlt')}
moduleClassName="StoryViewer__story"
queueStoryDownload={queueStoryDownload}
storyId={messageId}
/>
{group && (
<div className="StoryViewer__meta">
<Avatar
acceptedMessageRequest={group.acceptedMessageRequest}
avatarPath={group.avatarPath}
acceptedMessageRequest={acceptedMessageRequest}
avatarPath={avatarPath}
badge={undefined}
className="StoryViewer__meta--group-avatar"
color={getAvatarColor(group.color)}
conversationType="group"
color={getAvatarColor(color)}
conversationType="direct"
i18n={i18n}
isMe={false}
name={group.name}
profileName={group.profileName}
sharedGroupNames={group.sharedGroupNames}
isMe={Boolean(isMe)}
name={name}
profileName={profileName}
sharedGroupNames={sharedGroupNames}
size={AvatarSize.TWENTY_EIGHT}
title={group.title}
title={title}
/>
)}
<div className="StoryViewer__meta--title">
{group
? i18n('Stories__from-to-group', {
name: title,
group: group.title,
})
: title}
</div>
<MessageTimestamp
i18n={i18n}
module="StoryViewer__meta--timestamp"
timestamp={timestamp}
/>
<div className="StoryViewer__progress">
{stories.map((story, index) => (
<div
className="StoryViewer__progress--container"
key={story.messageId}
>
{currentStoryIndex === index ? (
<animated.div
className="StoryViewer__progress--bar"
style={{
width: to([styles.width], width => `${width}%`),
}}
/>
) : (
<div
className="StoryViewer__progress--bar"
style={{
width: currentStoryIndex < index ? '0%' : '100%',
}}
/>
)}
</div>
))}
{group && (
<Avatar
acceptedMessageRequest={group.acceptedMessageRequest}
avatarPath={group.avatarPath}
badge={undefined}
className="StoryViewer__meta--group-avatar"
color={getAvatarColor(group.color)}
conversationType="group"
i18n={i18n}
isMe={false}
name={group.name}
profileName={group.profileName}
sharedGroupNames={group.sharedGroupNames}
size={AvatarSize.TWENTY_EIGHT}
title={group.title}
/>
)}
<div className="StoryViewer__meta--title">
{group
? i18n('Stories__from-to-group', {
name: title,
group: group.title,
})
: title}
</div>
<MessageTimestamp
i18n={i18n}
module="StoryViewer__meta--timestamp"
timestamp={timestamp}
/>
<div className="StoryViewer__progress">
{stories.map((story, index) => (
<div
className="StoryViewer__progress--container"
key={story.messageId}
>
{currentStoryIndex === index ? (
<animated.div
className="StoryViewer__progress--bar"
style={{
width: to([styles.width], width => `${width}%`),
}}
/>
) : (
<div
className="StoryViewer__progress--bar"
style={{
width: currentStoryIndex < index ? '0%' : '100%',
}}
/>
)}
</div>
))}
</div>
</div>
</div>
<div className="StoryViewer__actions">
{isMe ? (
<>
{views &&
(views === 1 ? (
<Intl
i18n={i18n}
id="MyStories__views--singular"
components={[<strong>{views}</strong>]}
/>
) : (
<Intl
i18n={i18n}
id="MyStories__views--plural"
components={[<strong>{views}</strong>]}
/>
))}
{views && replies && ' '}
{replies &&
(replies === 1 ? (
<Intl
i18n={i18n}
id="MyStories__replies--singular"
components={[<strong>{replies}</strong>]}
/>
) : (
<Intl
i18n={i18n}
id="MyStories__replies--plural"
components={[<strong>{replies}</strong>]}
/>
))}
</>
) : (
<button
className="StoryViewer__reply"
onClick={() => setHasReplyModal(true)}
tabIndex={0}
type="button"
>
{i18n('StoryViewer__reply')}
</button>
)}
</div>
</div>
<div className="StoryViewer__actions">
{isMe ? (
<>
{views &&
(views === 1 ? (
<Intl
i18n={i18n}
id="MyStories__views--singular"
components={[<strong>{views}</strong>]}
/>
) : (
<Intl
i18n={i18n}
id="MyStories__views--plural"
components={[<strong>{views}</strong>]}
/>
))}
{views && replies && ' '}
{replies &&
(replies === 1 ? (
<Intl
i18n={i18n}
id="MyStories__replies--singular"
components={[<strong>{replies}</strong>]}
/>
) : (
<Intl
i18n={i18n}
id="MyStories__replies--plural"
components={[<strong>{replies}</strong>]}
/>
))}
</>
) : (
<button
className="StoryViewer__reply"
onClick={() => setHasReplyModal(true)}
type="button"
>
{i18n('StoryViewer__reply')}
</button>
)}
</div>
{hasReplyModal && (
<StoryViewsNRepliesModal
authorTitle={title}
getPreferredBadge={getPreferredBadge}
i18n={i18n}
isMyStory={isMe}
onClose={() => setHasReplyModal(false)}
onReact={emoji => {
onReactToStory(emoji, visibleStory);
}}
onReply={(message, mentions, replyTimestamp) => {
setHasReplyModal(false);
onReplyToStory(message, mentions, replyTimestamp, visibleStory);
}}
onSetSkinTone={onSetSkinTone}
onTextTooLong={onTextTooLong}
onUseEmoji={onUseEmoji}
preferredReactionEmoji={preferredReactionEmoji}
recentEmojis={recentEmojis}
renderEmojiPicker={renderEmojiPicker}
replies={[]}
skinTone={skinTone}
storyPreviewAttachment={attachment}
views={[]}
/>
)}
</div>
{hasReplyModal && (
<StoryViewsNRepliesModal
authorTitle={title}
getPreferredBadge={getPreferredBadge}
i18n={i18n}
isMyStory={isMe}
onClose={() => setHasReplyModal(false)}
onReact={emoji => {
onReactToStory(emoji, visibleStory);
}}
onReply={(message, mentions, replyTimestamp) => {
setHasReplyModal(false);
onReplyToStory(message, mentions, replyTimestamp, visibleStory);
}}
onSetSkinTone={onSetSkinTone}
onTextTooLong={onTextTooLong}
onUseEmoji={onUseEmoji}
preferredReactionEmoji={preferredReactionEmoji}
recentEmojis={recentEmojis}
renderEmojiPicker={renderEmojiPicker}
replies={[]}
skinTone={skinTone}
storyPreviewAttachment={attachment}
views={[]}
/>
)}
</div>
</FocusTrap>
);
};