diff --git a/_locales/en/messages.json b/_locales/en/messages.json index b021e5263739..f2c949fc4090 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -7018,6 +7018,14 @@ } } }, + "StoryViewer__pause": { + "message": "Pause", + "description": "Aria label for pausing a story" + }, + "StoryViewer__play": { + "message": "Play", + "description": "Aria label for playing a story" + }, "StoryViewer__reply": { "message": "Reply", "description": "Button label to reply to a story" @@ -7026,6 +7034,14 @@ "message": "Reply to Group", "description": "Button label to reply to a group story" }, + "StoryViewer__mute": { + "message": "Mute", + "description": "Aria label for muting stories" + }, + "StoryViewer__unmute": { + "message": "Unmute", + "description": "Aria label for unmuting stories" + }, "StoryViewsNRepliesModal__no-replies": { "message": "No replies yet", "description": "Placeholder text for when there are no replies" diff --git a/images/icons/v2/pause_solid_20.svg b/images/icons/v2/pause_solid_20.svg new file mode 100644 index 000000000000..19b4938ecc5d --- /dev/null +++ b/images/icons/v2/pause_solid_20.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/images/icons/v2/play_solid_20.svg b/images/icons/v2/play_solid_20.svg new file mode 100644 index 000000000000..e4ce1a1fe36e --- /dev/null +++ b/images/icons/v2/play_solid_20.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/images/icons/v2/speaker-off-solid-20.svg b/images/icons/v2/speaker-off-solid-20.svg new file mode 100644 index 000000000000..b97d7264c7f6 --- /dev/null +++ b/images/icons/v2/speaker-off-solid-20.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/images/icons/v2/speaker-on-solid-20.svg b/images/icons/v2/speaker-on-solid-20.svg new file mode 100644 index 000000000000..56f9b24cca7c --- /dev/null +++ b/images/icons/v2/speaker-on-solid-20.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/stylesheets/components/StoryViewer.scss b/stylesheets/components/StoryViewer.scss index 8e4a39919f20..fee1cdb0bf52 100644 --- a/stylesheets/components/StoryViewer.scss +++ b/stylesheets/components/StoryViewer.scss @@ -35,24 +35,6 @@ z-index: $z-index-above-above-base; } - &__more { - @include button-reset; - height: 24px; - position: absolute; - right: 80px; - top: var(--title-bar-drag-area-height); - width: 24px; - z-index: $z-index-above-base; - - @include color-svg('../images/icons/v2/more-horiz-24.svg', $color-white); - - @include keyboard-mode { - &:focus { - background-color: $color-ultramarine; - } - } - } - &__container { flex-grow: 1; overflow: hidden; @@ -89,6 +71,16 @@ @include font-body-2; color: $color-white-alpha-60; } + + &__playback-bar { + display: flex; + justify-content: space-between; + } + + &__playback-controls { + align-items: center; + display: flex; + } } &__caption { @@ -154,6 +146,76 @@ } } + &__more { + @include button-reset; + height: 24px; + width: 24px; + + @include color-svg('../images/icons/v2/more-horiz-24.svg', $color-white); + + @include keyboard-mode { + &:focus { + background-color: $color-black; + } + } + } + + &__mute { + @include button-reset; + height: 20px; + margin: 0 24px; + width: 20px; + @include color-svg( + '../images/icons/v2/speaker-on-solid-20.svg', + $color-white + ); + @include keyboard-mode { + &:focus { + background-color: $color-white-alpha-80; + } + } + } + + &__pause { + @include button-reset; + height: 20px; + width: 20px; + @include color-svg('../images/icons/v2/pause_solid_20.svg', $color-white); + @include keyboard-mode { + &:focus { + background-color: $color-white-alpha-80; + } + } + } + + &__play { + @include button-reset; + height: 20px; + width: 20px; + @include color-svg('../images/icons/v2/play_solid_20.svg', $color-white); + @include keyboard-mode { + &:focus { + background-color: $color-white-alpha-80; + } + } + } + + &__unmute { + @include button-reset; + height: 20px; + margin: 0 18px; + width: 20px; + @include color-svg( + '../images/icons/v2/speaker-off-solid-20.svg', + $color-white + ); + @include keyboard-mode { + &:focus { + background-color: $color-white-alpha-80; + } + } + } + &__progress { display: flex; diff --git a/ts/components/ContextMenu.tsx b/ts/components/ContextMenu.tsx index 784d6af9c0af..d75a9196ecc2 100644 --- a/ts/components/ContextMenu.tsx +++ b/ts/components/ContextMenu.tsx @@ -49,6 +49,7 @@ export function ContextMenuPopper({ onClose, referenceElement, title, + theme, value, }: ContextMenuPropsType): JSX.Element | null { const [popperElement, setPopperElement] = useState( @@ -84,49 +85,54 @@ export function ContextMenuPopper({ } return ( -
- {title &&
{title}
} - {menuOptions.map((option, index) => ( - - ))} + {typeof value !== 'undefined' && + typeof option.value !== 'undefined' && + value === option.value ? ( +
+ ) : null} + + ))} +
); } diff --git a/ts/components/StoryImage.tsx b/ts/components/StoryImage.tsx index 7174d07ffa74..6032655b16cf 100644 --- a/ts/components/StoryImage.tsx +++ b/ts/components/StoryImage.tsx @@ -25,6 +25,7 @@ export type PropsType = { readonly attachment?: AttachmentType; readonly children?: ReactNode; readonly i18n: LocalizerType; + readonly isMuted?: boolean; readonly isPaused?: boolean; readonly isThumbnail?: boolean; readonly label: string; @@ -37,6 +38,7 @@ export const StoryImage = ({ attachment, children, i18n, + isMuted, isPaused, isThumbnail, label, @@ -106,6 +108,7 @@ export const StoryImage = ({ controls={false} key={attachment.url} loop={shouldLoop} + muted={isMuted} ref={videoRef} > diff --git a/ts/components/StoryViewer.stories.tsx b/ts/components/StoryViewer.stories.tsx index f64c6695d547..1fc04654e97d 100644 --- a/ts/components/StoryViewer.stories.tsx +++ b/ts/components/StoryViewer.stories.tsx @@ -23,6 +23,7 @@ function getDefaultProps(): PropsType { conversationId: sender.id, getPreferredBadge: () => undefined, group: undefined, + hasAllStoriesMuted: false, i18n, loadStoryReplies: action('loadStoryReplies'), markStoryRead: action('markStoryRead'), @@ -51,6 +52,7 @@ function getDefaultProps(): PropsType { timestamp: Date.now(), }, ], + toggleHasAllStoriesMuted: action('toggleHasAllStoriesMuted'), }; } @@ -153,6 +155,7 @@ story.add('Caption', () => ( story.add('Long Caption', () => ( ; + hasAllStoriesMuted: boolean; i18n: LocalizerType; loadStoryReplies: (conversationId: string, messageId: string) => unknown; markStoryRead: (mId: string) => unknown; @@ -72,6 +74,7 @@ export type PropsType = { replyState?: ReplyStateType; skinTone?: number; stories: Array; + toggleHasAllStoriesMuted: () => unknown; views?: Array; }; @@ -90,6 +93,7 @@ export const StoryViewer = ({ conversationId, getPreferredBadge, group, + hasAllStoriesMuted, i18n, loadStoryReplies, markStoryRead, @@ -110,6 +114,7 @@ export const StoryViewer = ({ replyState, skinTone, stories, + toggleHasAllStoriesMuted, views, }: PropsType): JSX.Element => { const [currentStoryIndex, setCurrentStoryIndex] = useState(0); @@ -261,11 +266,14 @@ export const StoryViewer = ({ }; }, [currentStoryIndex, spring, storyDuration]); + const [pauseStory, setPauseStory] = useState(false); + const shouldPauseViewing = hasConfirmHideStory || hasExpandedCaption || hasReplyModal || isShowingContextMenu || + pauseStory || Boolean(reactionEmoji); useEffect(() => { @@ -388,6 +396,7 @@ export const StoryViewer = ({ attachment={attachment} i18n={i18n} isPaused={shouldPauseViewing} + isMuted={hasAllStoriesMuted} label={i18n('lightboxImageAlt')} moduleClassName="StoryViewer__story" queueStoryDownload={queueStoryDownload} @@ -436,50 +445,90 @@ export const StoryViewer = ({ )} )} - - {group && ( - - )} -
- {group - ? i18n('Stories__from-to-group', { - name: title, - group: group.title, - }) - : title} +
+
+ + {group && ( + + )} +
+ {group + ? i18n('Stories__from-to-group', { + name: title, + group: group.title, + }) + : title} +
+ +
+
+
-
{stories.map((story, index) => (
-