Update conversation header design
This commit is contained in:
parent
d7d70da315
commit
dfa5005e7d
8 changed files with 514 additions and 487 deletions
|
@ -2853,336 +2853,6 @@ $timer-icons: '55', '50', '45', '40', '35', '30', '25', '20', '15', '10', '05',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Module: Conversation Header
|
|
||||||
|
|
||||||
.module-conversation-header {
|
|
||||||
padding-left: 16px;
|
|
||||||
padding-right: 16px;
|
|
||||||
padding-top: var(--title-bar-drag-area-height);
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
height: calc(#{$header-height} + var(--title-bar-drag-area-height));
|
|
||||||
|
|
||||||
@include light-theme {
|
|
||||||
color: $color-gray-90;
|
|
||||||
background-color: $color-white;
|
|
||||||
}
|
|
||||||
@include dark-theme {
|
|
||||||
color: $color-gray-02;
|
|
||||||
background-color: $color-gray-95;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.module-conversation-header__back-icon {
|
|
||||||
$transition: 250ms ease-out;
|
|
||||||
|
|
||||||
display: inline-block;
|
|
||||||
margin-left: -10px;
|
|
||||||
margin-right: -10px;
|
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
min-width: 24px;
|
|
||||||
vertical-align: text-bottom;
|
|
||||||
border: none;
|
|
||||||
opacity: 0;
|
|
||||||
transition: margin-right $transition, opacity $transition;
|
|
||||||
|
|
||||||
&:disabled {
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--show {
|
|
||||||
opacity: 1;
|
|
||||||
margin-right: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@include light-theme {
|
|
||||||
@include color-svg(
|
|
||||||
'../images/icons/v2/chevron-left-24.svg',
|
|
||||||
$color-gray-90
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@include dark-theme {
|
|
||||||
@include color-svg(
|
|
||||||
'../images/icons/v2/chevron-left-24.svg',
|
|
||||||
$color-gray-02
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.module-conversation-header__title-container {
|
|
||||||
flex-grow: 1;
|
|
||||||
flex-shrink: 1;
|
|
||||||
min-width: 0;
|
|
||||||
display: block;
|
|
||||||
|
|
||||||
height: $header-height;
|
|
||||||
}
|
|
||||||
|
|
||||||
.module-conversation-header__title-flex {
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
display: inline-flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: center;
|
|
||||||
height: $header-height;
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.module-conversation-header__title-clickable {
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
&:focus {
|
|
||||||
@include mouse-mode {
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.module-conversation-header__note-to-self {
|
|
||||||
@include dark-theme {
|
|
||||||
color: $color-gray-02;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.module-conversation-header__avatar {
|
|
||||||
min-width: 32px;
|
|
||||||
margin-right: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.module-conversation-header__title {
|
|
||||||
margin-left: 6px;
|
|
||||||
min-width: 0;
|
|
||||||
|
|
||||||
@include font-body-1-bold;
|
|
||||||
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
|
|
||||||
-webkit-user-select: text;
|
|
||||||
|
|
||||||
@include light-theme {
|
|
||||||
color: $color-gray-90;
|
|
||||||
}
|
|
||||||
@include dark-theme {
|
|
||||||
color: $color-gray-02;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.module-conversation-header__contacts-icon {
|
|
||||||
display: inline-block;
|
|
||||||
height: 15px;
|
|
||||||
width: 15px;
|
|
||||||
|
|
||||||
margin-bottom: 3px;
|
|
||||||
vertical-align: middle;
|
|
||||||
|
|
||||||
@include light-theme {
|
|
||||||
@include color-svg(
|
|
||||||
'../images/icons/v2/profile-circle-outline-24.svg',
|
|
||||||
$color-gray-60
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@include dark-theme {
|
|
||||||
@include color-svg(
|
|
||||||
'../images/icons/v2/profile-circle-outline-24.svg',
|
|
||||||
$color-gray-25
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@include keyboard-mode {
|
|
||||||
&:focus {
|
|
||||||
@include color-svg(
|
|
||||||
'../images/icons/v2/profile-circle-outline-24.svg',
|
|
||||||
$ultramarine-ui-light
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.module-conversation-header__title__profile-name {
|
|
||||||
@include font-body-1-bold-italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
.module-conversation-header__title__verified-icon {
|
|
||||||
display: inline-block;
|
|
||||||
width: 1.25em;
|
|
||||||
height: 1.25em;
|
|
||||||
vertical-align: text-bottom;
|
|
||||||
|
|
||||||
@include light-theme {
|
|
||||||
@include color-svg('../images/icons/v2/check-24.svg', $color-gray-90);
|
|
||||||
}
|
|
||||||
@include dark-theme {
|
|
||||||
@include color-svg('../images/icons/v2/check-24.svg', $color-gray-02);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.module-conversation-header__expiration {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: center;
|
|
||||||
padding-left: 8px;
|
|
||||||
padding-right: 8px;
|
|
||||||
transition: opacity 250ms ease-out;
|
|
||||||
|
|
||||||
&--hidden {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.module-conversation-header__expiration__clock-icon {
|
|
||||||
height: 24px;
|
|
||||||
width: 24px;
|
|
||||||
display: inline-block;
|
|
||||||
|
|
||||||
@include light-theme {
|
|
||||||
@include color-svg('../images/icons/v2/timer-24.svg', $color-gray-60);
|
|
||||||
}
|
|
||||||
@include dark-theme {
|
|
||||||
@include color-svg('../images/icons/v2/timer-24.svg', $color-gray-25);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.module-conversation-header__expiration__setting {
|
|
||||||
margin-left: 5px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.module-conversation-header__more-button {
|
|
||||||
height: 24px;
|
|
||||||
width: 24px;
|
|
||||||
margin-left: 12px;
|
|
||||||
border: none;
|
|
||||||
opacity: 0;
|
|
||||||
transition: opacity 250ms ease-out;
|
|
||||||
|
|
||||||
&:disabled {
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--show {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
@include light-theme {
|
|
||||||
@include color-svg(
|
|
||||||
'../images/icons/v2/chevron-down-24.svg',
|
|
||||||
$color-gray-75
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@include dark-theme {
|
|
||||||
@include color-svg(
|
|
||||||
'../images/icons/v2/chevron-down-24.svg',
|
|
||||||
$color-gray-15
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.module-conversation-header__search-button {
|
|
||||||
height: 24px;
|
|
||||||
width: 24px;
|
|
||||||
margin-left: 12px;
|
|
||||||
border: none;
|
|
||||||
opacity: 0;
|
|
||||||
transition: opacity 250ms ease-out;
|
|
||||||
|
|
||||||
&:disabled {
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--show {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
@include light-theme {
|
|
||||||
@include color-svg('../images/icons/v2/search-24.svg', $color-gray-75);
|
|
||||||
}
|
|
||||||
@include dark-theme {
|
|
||||||
@include color-svg('../images/icons/v2/search-24.svg', $color-gray-15);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.module-conversation-header__calling-button {
|
|
||||||
$icon-size: 24px;
|
|
||||||
|
|
||||||
margin-left: 12px;
|
|
||||||
border: none;
|
|
||||||
opacity: 0;
|
|
||||||
transition: opacity 250ms ease-out;
|
|
||||||
|
|
||||||
&:disabled {
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--show {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--video {
|
|
||||||
@include light-theme {
|
|
||||||
@include color-svg(
|
|
||||||
'../images/icons/v2/video-outline-24.svg',
|
|
||||||
$color-gray-75
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@include dark-theme {
|
|
||||||
@include color-svg(
|
|
||||||
'../images/icons/v2/video-solid-24.svg',
|
|
||||||
$color-gray-15
|
|
||||||
);
|
|
||||||
}
|
|
||||||
height: $icon-size;
|
|
||||||
width: $icon-size;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--audio {
|
|
||||||
@include light-theme {
|
|
||||||
@include color-svg(
|
|
||||||
'../images/icons/v2/phone-right-outline-24.svg',
|
|
||||||
$color-gray-75
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@include dark-theme {
|
|
||||||
@include color-svg(
|
|
||||||
'../images/icons/v2/phone-right-solid-24.svg',
|
|
||||||
$color-gray-15
|
|
||||||
);
|
|
||||||
}
|
|
||||||
height: $icon-size;
|
|
||||||
width: $icon-size;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--join {
|
|
||||||
@include font-body-1;
|
|
||||||
align-items: center;
|
|
||||||
background-color: $color-accent-green;
|
|
||||||
border-radius: 9999px; // This ensures the borders are completely rounded. (A value like 100% would make it an ellipse.)
|
|
||||||
color: $color-white;
|
|
||||||
display: flex;
|
|
||||||
outline: none;
|
|
||||||
padding: 5px 18px;
|
|
||||||
|
|
||||||
@include keyboard-mode {
|
|
||||||
&:focus {
|
|
||||||
box-shadow: 0px 0px 0px 4px $ultramarine-ui-light;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&:before {
|
|
||||||
@include color-svg('../images/icons/v2/video-solid-24.svg', $color-white);
|
|
||||||
content: '';
|
|
||||||
display: block;
|
|
||||||
height: $icon-size;
|
|
||||||
margin-right: 5px;
|
|
||||||
width: $icon-size;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Module: Conversation Details
|
// Module: Conversation Details
|
||||||
|
|
||||||
.conversation-details-panel {
|
.conversation-details-panel {
|
||||||
|
|
330
stylesheets/components/ConversationHeader.scss
Normal file
330
stylesheets/components/ConversationHeader.scss
Normal file
|
@ -0,0 +1,330 @@
|
||||||
|
// Copyright 2021 Signal Messenger, LLC
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
.module-ConversationHeader {
|
||||||
|
--button-spacing: 24px;
|
||||||
|
|
||||||
|
&.module-ConversationHeader--narrow {
|
||||||
|
--button-spacing: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
padding-top: var(--title-bar-drag-area-height);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
height: calc(#{$header-height} + var(--title-bar-drag-area-height));
|
||||||
|
|
||||||
|
@include light-theme {
|
||||||
|
color: $color-gray-90;
|
||||||
|
background-color: $color-white;
|
||||||
|
}
|
||||||
|
@include dark-theme {
|
||||||
|
color: $color-gray-02;
|
||||||
|
background-color: $color-gray-95;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__back-icon {
|
||||||
|
$transition: 250ms ease-out;
|
||||||
|
|
||||||
|
display: inline-block;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
min-width: 24px;
|
||||||
|
margin-left: -24px;
|
||||||
|
vertical-align: text-bottom;
|
||||||
|
border: none;
|
||||||
|
opacity: 0;
|
||||||
|
transition: margin-left $transition, opacity $transition;
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--show {
|
||||||
|
opacity: 1;
|
||||||
|
margin-right: 6px;
|
||||||
|
margin-left: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include light-theme {
|
||||||
|
@include color-svg(
|
||||||
|
'../images/icons/v2/chevron-left-24.svg',
|
||||||
|
$color-gray-90
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@include dark-theme {
|
||||||
|
@include color-svg(
|
||||||
|
'../images/icons/v2/chevron-left-24.svg',
|
||||||
|
$color-gray-02
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__header {
|
||||||
|
$padding: 4px 12px;
|
||||||
|
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-grow: 1;
|
||||||
|
margin-left: 4px;
|
||||||
|
margin-right: var(--button-spacing);
|
||||||
|
padding: $padding;
|
||||||
|
overflow: hidden;
|
||||||
|
min-width: 0;
|
||||||
|
transition: margin-right 200ms ease-out;
|
||||||
|
|
||||||
|
&--clickable {
|
||||||
|
@include button-reset;
|
||||||
|
border-radius: 4px;
|
||||||
|
|
||||||
|
// These are clobbered by button-reset:
|
||||||
|
margin-left: 4px;
|
||||||
|
margin-right: var(--button-spacing);
|
||||||
|
padding: $padding;
|
||||||
|
|
||||||
|
@include keyboard-mode {
|
||||||
|
&:focus {
|
||||||
|
color: $ultramarine-ui-light;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@include dark-keyboard-mode {
|
||||||
|
&:focus {
|
||||||
|
color: $ultramarine-ui-dark;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__avatar {
|
||||||
|
min-width: 32px;
|
||||||
|
margin-right: 12px;
|
||||||
|
padding-top: 4px;
|
||||||
|
padding-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__info {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-width: 0;
|
||||||
|
|
||||||
|
&__title {
|
||||||
|
@include font-body-1-bold;
|
||||||
|
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
|
&__in-contacts-icon {
|
||||||
|
margin-left: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__subtitle {
|
||||||
|
display: flex;
|
||||||
|
@include font-body-2;
|
||||||
|
|
||||||
|
@include light-theme {
|
||||||
|
color: $color-gray-60;
|
||||||
|
}
|
||||||
|
@include dark-theme {
|
||||||
|
color: $color-gray-25;
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin subtitle-element($icon) {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
user-select: none;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
width: 13px;
|
||||||
|
height: 13px;
|
||||||
|
display: block;
|
||||||
|
margin-right: 4px;
|
||||||
|
|
||||||
|
@include light-theme {
|
||||||
|
@include color-svg($icon, $color-gray-60);
|
||||||
|
}
|
||||||
|
@include dark-theme {
|
||||||
|
@include color-svg($icon, $color-gray-25);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__expiration {
|
||||||
|
@include subtitle-element('../images/icons/v2/timer-24.svg');
|
||||||
|
margin-right: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__verified {
|
||||||
|
@include subtitle-element('../images/icons/v2/check-24.svg');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__calling-button--video,
|
||||||
|
&__calling-button--audio,
|
||||||
|
&__calling-button--join,
|
||||||
|
&__search-button,
|
||||||
|
&__more-button {
|
||||||
|
margin-right: var(--button-spacing);
|
||||||
|
transition: margin-right 200ms ease-out;
|
||||||
|
|
||||||
|
@include keyboard-mode {
|
||||||
|
&:focus {
|
||||||
|
background: $ultramarine-ui-light;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@include dark-keyboard-mode {
|
||||||
|
&:focus {
|
||||||
|
background: $ultramarine-ui-dark;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__calling-button {
|
||||||
|
$icon-size: 24px;
|
||||||
|
|
||||||
|
border: none;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 250ms ease-out;
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--show {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--video {
|
||||||
|
@include light-theme {
|
||||||
|
@include color-svg(
|
||||||
|
'../images/icons/v2/video-outline-24.svg',
|
||||||
|
$color-gray-75
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@include dark-theme {
|
||||||
|
@include color-svg(
|
||||||
|
'../images/icons/v2/video-solid-24.svg',
|
||||||
|
$color-gray-15
|
||||||
|
);
|
||||||
|
}
|
||||||
|
height: $icon-size;
|
||||||
|
width: $icon-size;
|
||||||
|
min-width: $icon-size;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--audio {
|
||||||
|
@include light-theme {
|
||||||
|
@include color-svg(
|
||||||
|
'../images/icons/v2/phone-right-outline-24.svg',
|
||||||
|
$color-gray-75
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@include dark-theme {
|
||||||
|
@include color-svg(
|
||||||
|
'../images/icons/v2/phone-right-solid-24.svg',
|
||||||
|
$color-gray-15
|
||||||
|
);
|
||||||
|
}
|
||||||
|
height: $icon-size;
|
||||||
|
width: $icon-size;
|
||||||
|
min-width: $icon-size;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--join {
|
||||||
|
@include font-body-1;
|
||||||
|
align-items: center;
|
||||||
|
background-color: $color-accent-green;
|
||||||
|
border-radius: 9999px; // This ensures the borders are completely rounded. (A value like 100% would make it an ellipse.)
|
||||||
|
color: $color-white;
|
||||||
|
display: flex;
|
||||||
|
outline: none;
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 5px 18px;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
|
@include keyboard-mode {
|
||||||
|
&:focus {
|
||||||
|
box-shadow: 0px 0px 0px 4px $ultramarine-ui-light;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
@include color-svg(
|
||||||
|
'../images/icons/v2/video-solid-24.svg',
|
||||||
|
$color-white
|
||||||
|
);
|
||||||
|
content: '';
|
||||||
|
display: block;
|
||||||
|
height: $icon-size;
|
||||||
|
margin-right: 5px;
|
||||||
|
min-width: $icon-size;
|
||||||
|
width: $icon-size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__search-button {
|
||||||
|
height: 24px;
|
||||||
|
width: 24px;
|
||||||
|
min-width: 24px;
|
||||||
|
border: none;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 250ms ease-out;
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--show {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include light-theme {
|
||||||
|
@include color-svg('../images/icons/v2/search-24.svg', $color-gray-75);
|
||||||
|
}
|
||||||
|
@include dark-theme {
|
||||||
|
@include color-svg('../images/icons/v2/search-24.svg', $color-gray-15);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__more-button {
|
||||||
|
height: 24px;
|
||||||
|
width: 24px;
|
||||||
|
min-width: 24px;
|
||||||
|
border: none;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 250ms ease-out;
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--show {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include light-theme {
|
||||||
|
@include color-svg(
|
||||||
|
'../images/icons/v2/chevron-down-24.svg',
|
||||||
|
$color-gray-75
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@include dark-theme {
|
||||||
|
@include color-svg(
|
||||||
|
'../images/icons/v2/chevron-down-24.svg',
|
||||||
|
$color-gray-15
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -28,3 +28,4 @@
|
||||||
|
|
||||||
// New style: components
|
// New style: components
|
||||||
@import './components/Button.scss';
|
@import './components/Button.scss';
|
||||||
|
@import './components/ConversationHeader.scss';
|
||||||
|
|
|
@ -1070,7 +1070,7 @@ export async function startApp(): Promise<void> {
|
||||||
(key === 'l' || key === 'L')
|
(key === 'l' || key === 'L')
|
||||||
) {
|
) {
|
||||||
const button = document.querySelector(
|
const button = document.querySelector(
|
||||||
'.module-conversation-header__more-button'
|
'.module-ConversationHeader__more-button'
|
||||||
);
|
);
|
||||||
if (!button) {
|
if (!button) {
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -22,6 +22,14 @@ const TooltipEventWrapper = React.forwardRef<
|
||||||
>(({ onHoverChanged, children }, ref) => {
|
>(({ onHoverChanged, children }, ref) => {
|
||||||
const wrapperRef = React.useRef<HTMLSpanElement | null>(null);
|
const wrapperRef = React.useRef<HTMLSpanElement | null>(null);
|
||||||
|
|
||||||
|
const on = React.useCallback(() => {
|
||||||
|
onHoverChanged(true);
|
||||||
|
}, [onHoverChanged]);
|
||||||
|
|
||||||
|
const off = React.useCallback(() => {
|
||||||
|
onHoverChanged(false);
|
||||||
|
}, [onHoverChanged]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
const wrapperEl = wrapperRef.current;
|
const wrapperEl = wrapperRef.current;
|
||||||
|
|
||||||
|
@ -29,28 +37,19 @@ const TooltipEventWrapper = React.forwardRef<
|
||||||
return noop;
|
return noop;
|
||||||
}
|
}
|
||||||
|
|
||||||
const on = () => {
|
|
||||||
onHoverChanged(true);
|
|
||||||
};
|
|
||||||
const off = () => {
|
|
||||||
onHoverChanged(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
wrapperEl.addEventListener('focus', on);
|
|
||||||
wrapperEl.addEventListener('blur', off);
|
|
||||||
wrapperEl.addEventListener('mouseenter', on);
|
wrapperEl.addEventListener('mouseenter', on);
|
||||||
wrapperEl.addEventListener('mouseleave', off);
|
wrapperEl.addEventListener('mouseleave', off);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
wrapperEl.removeEventListener('focus', on);
|
|
||||||
wrapperEl.removeEventListener('blur', off);
|
|
||||||
wrapperEl.removeEventListener('mouseenter', on);
|
wrapperEl.removeEventListener('mouseenter', on);
|
||||||
wrapperEl.removeEventListener('mouseleave', off);
|
wrapperEl.removeEventListener('mouseleave', off);
|
||||||
};
|
};
|
||||||
}, [onHoverChanged]);
|
}, [on, off]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span
|
<span
|
||||||
|
onFocus={on}
|
||||||
|
onBlur={off}
|
||||||
// This is a forward ref that also needs a ref of its own, so we set both here.
|
// This is a forward ref that also needs a ref of its own, so we set both here.
|
||||||
ref={el => {
|
ref={el => {
|
||||||
wrapperRef.current = el;
|
wrapperRef.current = el;
|
||||||
|
|
|
@ -159,6 +159,20 @@ const stories: Array<ConversationHeaderStory> = [
|
||||||
acceptedMessageRequest: true,
|
acceptedMessageRequest: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: 'Disappearing messages + verified',
|
||||||
|
props: {
|
||||||
|
...commonProps,
|
||||||
|
color: 'indigo',
|
||||||
|
title: '(202) 555-0005',
|
||||||
|
phoneNumber: '(202) 555-0005',
|
||||||
|
type: 'direct',
|
||||||
|
id: '5',
|
||||||
|
expireTimer: 60,
|
||||||
|
acceptedMessageRequest: true,
|
||||||
|
isVerified: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: 'Muting Conversation',
|
title: 'Muting Conversation',
|
||||||
props: {
|
props: {
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
// Copyright 2018-2020 Signal Messenger, LLC
|
// Copyright 2018-2021 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import React from 'react';
|
import React, { ReactNode } from 'react';
|
||||||
|
import Measure from 'react-measure';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import {
|
import {
|
||||||
|
@ -92,27 +93,33 @@ export type PropsType = PropsDataType &
|
||||||
PropsActionsType &
|
PropsActionsType &
|
||||||
PropsHousekeepingType;
|
PropsHousekeepingType;
|
||||||
|
|
||||||
export class ConversationHeader extends React.Component<PropsType> {
|
type StateType = {
|
||||||
public showMenuBound: (event: React.MouseEvent<HTMLButtonElement>) => void;
|
isNarrow: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class ConversationHeader extends React.Component<PropsType, StateType> {
|
||||||
|
private showMenuBound: (event: React.MouseEvent<HTMLButtonElement>) => void;
|
||||||
|
|
||||||
// Comes from a third-party dependency
|
// Comes from a third-party dependency
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
public menuTriggerRef: React.RefObject<any>;
|
private menuTriggerRef: React.RefObject<any>;
|
||||||
|
|
||||||
public constructor(props: PropsType) {
|
public constructor(props: PropsType) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
|
this.state = { isNarrow: false };
|
||||||
|
|
||||||
this.menuTriggerRef = React.createRef();
|
this.menuTriggerRef = React.createRef();
|
||||||
this.showMenuBound = this.showMenu.bind(this);
|
this.showMenuBound = this.showMenu.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public showMenu(event: React.MouseEvent<HTMLButtonElement>): void {
|
private showMenu(event: React.MouseEvent<HTMLButtonElement>): void {
|
||||||
if (this.menuTriggerRef.current) {
|
if (this.menuTriggerRef.current) {
|
||||||
this.menuTriggerRef.current.handleContextClick(event);
|
this.menuTriggerRef.current.handleContextClick(event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public renderBackButton(): JSX.Element {
|
private renderBackButton(): ReactNode {
|
||||||
const { i18n, onGoBack, showBackButton } = this.props;
|
const { i18n, onGoBack, showBackButton } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -120,8 +127,8 @@ export class ConversationHeader extends React.Component<PropsType> {
|
||||||
type="button"
|
type="button"
|
||||||
onClick={onGoBack}
|
onClick={onGoBack}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'module-conversation-header__back-icon',
|
'module-ConversationHeader__back-icon',
|
||||||
showBackButton ? 'module-conversation-header__back-icon--show' : null
|
showBackButton ? 'module-ConversationHeader__back-icon--show' : null
|
||||||
)}
|
)}
|
||||||
disabled={!showBackButton}
|
disabled={!showBackButton}
|
||||||
aria-label={i18n('goBack')}
|
aria-label={i18n('goBack')}
|
||||||
|
@ -129,51 +136,49 @@ export class ConversationHeader extends React.Component<PropsType> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public renderTitle(): JSX.Element | null {
|
private renderHeaderInfoTitle(): ReactNode {
|
||||||
const {
|
const { name, title, type, i18n, isMe } = this.props;
|
||||||
name,
|
|
||||||
phoneNumber,
|
|
||||||
title,
|
|
||||||
type,
|
|
||||||
i18n,
|
|
||||||
isMe,
|
|
||||||
profileName,
|
|
||||||
isVerified,
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
if (isMe) {
|
if (isMe) {
|
||||||
return (
|
return (
|
||||||
<div className="module-conversation-header__title">
|
<div className="module-ConversationHeader__header__info__title">
|
||||||
{i18n('noteToSelf')}
|
{i18n('noteToSelf')}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const shouldShowIcon = Boolean(name && type === 'direct');
|
const shouldShowIcon = Boolean(name && type === 'direct');
|
||||||
const shouldShowNumber = Boolean(phoneNumber && (name || profileName));
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="module-conversation-header__title">
|
<div className="module-ConversationHeader__header__info__title">
|
||||||
<Emojify text={title} />
|
<Emojify text={title} />
|
||||||
{shouldShowIcon ? (
|
{shouldShowIcon ? (
|
||||||
<span>
|
<InContactsIcon
|
||||||
{' '}
|
className="module-ConversationHeader__header__info__title__in-contacts-icon"
|
||||||
<InContactsIcon i18n={i18n} />
|
i18n={i18n}
|
||||||
</span>
|
/>
|
||||||
) : null}
|
|
||||||
{shouldShowNumber ? ` · ${phoneNumber}` : null}
|
|
||||||
{isVerified ? (
|
|
||||||
<span>
|
|
||||||
{' · '}
|
|
||||||
<span className="module-conversation-header__title__verified-icon" />
|
|
||||||
{i18n('verified')}
|
|
||||||
</span>
|
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public renderAvatar(): JSX.Element {
|
private renderHeaderInfoSubtitle(): ReactNode {
|
||||||
|
const expirationNode = this.renderExpirationLength();
|
||||||
|
const verifiedNode = this.renderVerifiedIcon();
|
||||||
|
|
||||||
|
if (expirationNode || verifiedNode) {
|
||||||
|
return (
|
||||||
|
<div className="module-ConversationHeader__header__info__subtitle">
|
||||||
|
{expirationNode}
|
||||||
|
{verifiedNode}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private renderAvatar(): ReactNode {
|
||||||
const {
|
const {
|
||||||
avatarPath,
|
avatarPath,
|
||||||
color,
|
color,
|
||||||
|
@ -187,7 +192,7 @@ export class ConversationHeader extends React.Component<PropsType> {
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span className="module-conversation-header__avatar">
|
<span className="module-ConversationHeader__header__avatar">
|
||||||
<Avatar
|
<Avatar
|
||||||
avatarPath={avatarPath}
|
avatarPath={avatarPath}
|
||||||
color={color}
|
color={color}
|
||||||
|
@ -204,34 +209,38 @@ export class ConversationHeader extends React.Component<PropsType> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public renderExpirationLength(): JSX.Element | null {
|
private renderExpirationLength(): ReactNode {
|
||||||
const { i18n, expireTimer, showBackButton } = this.props;
|
const { i18n, expireTimer } = this.props;
|
||||||
|
|
||||||
const expirationSettingName = expireTimer
|
const expirationSettingName = expireTimer
|
||||||
? ExpirationTimerOptions.getName(i18n, expireTimer)
|
? ExpirationTimerOptions.getAbbreviated(i18n, expireTimer)
|
||||||
: undefined;
|
: undefined;
|
||||||
if (!expirationSettingName) {
|
if (!expirationSettingName) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div className="module-ConversationHeader__header__info__subtitle__expiration">
|
||||||
className={classNames(
|
{expirationSettingName}
|
||||||
'module-conversation-header__expiration',
|
|
||||||
showBackButton
|
|
||||||
? 'module-conversation-header__expiration--hidden'
|
|
||||||
: null
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<div className="module-conversation-header__expiration__clock-icon" />
|
|
||||||
<div className="module-conversation-header__expiration__setting">
|
|
||||||
{expirationSettingName}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public renderMoreButton(triggerId: string): JSX.Element {
|
private renderVerifiedIcon(): ReactNode {
|
||||||
|
const { i18n, isVerified } = this.props;
|
||||||
|
|
||||||
|
if (!isVerified) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="module-ConversationHeader__header__info__subtitle__verified">
|
||||||
|
{i18n('verified')}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private renderMoreButton(triggerId: string): ReactNode {
|
||||||
const { i18n, showBackButton } = this.props;
|
const { i18n, showBackButton } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -240,10 +249,10 @@ export class ConversationHeader extends React.Component<PropsType> {
|
||||||
type="button"
|
type="button"
|
||||||
onClick={this.showMenuBound}
|
onClick={this.showMenuBound}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'module-conversation-header__more-button',
|
'module-ConversationHeader__more-button',
|
||||||
showBackButton
|
showBackButton
|
||||||
? null
|
? null
|
||||||
: 'module-conversation-header__more-button--show'
|
: 'module-ConversationHeader__more-button--show'
|
||||||
)}
|
)}
|
||||||
disabled={showBackButton}
|
disabled={showBackButton}
|
||||||
aria-label={i18n('moreInfo')}
|
aria-label={i18n('moreInfo')}
|
||||||
|
@ -252,7 +261,7 @@ export class ConversationHeader extends React.Component<PropsType> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public renderSearchButton(): JSX.Element {
|
private renderSearchButton(): ReactNode {
|
||||||
const { i18n, onSearchInConversation, showBackButton } = this.props;
|
const { i18n, onSearchInConversation, showBackButton } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -260,10 +269,10 @@ export class ConversationHeader extends React.Component<PropsType> {
|
||||||
type="button"
|
type="button"
|
||||||
onClick={onSearchInConversation}
|
onClick={onSearchInConversation}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'module-conversation-header__search-button',
|
'module-ConversationHeader__search-button',
|
||||||
showBackButton
|
showBackButton
|
||||||
? null
|
? null
|
||||||
: 'module-conversation-header__search-button--show'
|
: 'module-ConversationHeader__search-button--show'
|
||||||
)}
|
)}
|
||||||
disabled={showBackButton}
|
disabled={showBackButton}
|
||||||
aria-label={i18n('search')}
|
aria-label={i18n('search')}
|
||||||
|
@ -271,7 +280,7 @@ export class ConversationHeader extends React.Component<PropsType> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderOutgoingCallButtons(): JSX.Element | null {
|
private renderOutgoingCallButtons(): ReactNode {
|
||||||
const {
|
const {
|
||||||
i18n,
|
i18n,
|
||||||
onOutgoingAudioCallInConversation,
|
onOutgoingAudioCallInConversation,
|
||||||
|
@ -279,17 +288,18 @@ export class ConversationHeader extends React.Component<PropsType> {
|
||||||
outgoingCallButtonStyle,
|
outgoingCallButtonStyle,
|
||||||
showBackButton,
|
showBackButton,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
const { isNarrow } = this.state;
|
||||||
|
|
||||||
const videoButton = (
|
const videoButton = (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={onOutgoingVideoCallInConversation}
|
onClick={onOutgoingVideoCallInConversation}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'module-conversation-header__calling-button',
|
'module-ConversationHeader__calling-button',
|
||||||
'module-conversation-header__calling-button--video',
|
'module-ConversationHeader__calling-button--video',
|
||||||
showBackButton
|
showBackButton
|
||||||
? null
|
? null
|
||||||
: 'module-conversation-header__calling-button--show'
|
: 'module-ConversationHeader__calling-button--show'
|
||||||
)}
|
)}
|
||||||
disabled={showBackButton}
|
disabled={showBackButton}
|
||||||
aria-label={i18n('makeOutgoingVideoCall')}
|
aria-label={i18n('makeOutgoingVideoCall')}
|
||||||
|
@ -309,11 +319,11 @@ export class ConversationHeader extends React.Component<PropsType> {
|
||||||
type="button"
|
type="button"
|
||||||
onClick={onOutgoingAudioCallInConversation}
|
onClick={onOutgoingAudioCallInConversation}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'module-conversation-header__calling-button',
|
'module-ConversationHeader__calling-button',
|
||||||
'module-conversation-header__calling-button--audio',
|
'module-ConversationHeader__calling-button--audio',
|
||||||
showBackButton
|
showBackButton
|
||||||
? null
|
? null
|
||||||
: 'module-conversation-header__calling-button--show'
|
: 'module-ConversationHeader__calling-button--show'
|
||||||
)}
|
)}
|
||||||
disabled={showBackButton}
|
disabled={showBackButton}
|
||||||
aria-label={i18n('makeOutgoingCall')}
|
aria-label={i18n('makeOutgoingCall')}
|
||||||
|
@ -323,18 +333,19 @@ export class ConversationHeader extends React.Component<PropsType> {
|
||||||
case OutgoingCallButtonStyle.Join:
|
case OutgoingCallButtonStyle.Join:
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
|
aria-label={i18n('joinOngoingCall')}
|
||||||
type="button"
|
type="button"
|
||||||
onClick={onOutgoingVideoCallInConversation}
|
onClick={onOutgoingVideoCallInConversation}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'module-conversation-header__calling-button',
|
'module-ConversationHeader__calling-button',
|
||||||
'module-conversation-header__calling-button--join',
|
'module-ConversationHeader__calling-button--join',
|
||||||
showBackButton
|
showBackButton
|
||||||
? null
|
? null
|
||||||
: 'module-conversation-header__calling-button--show'
|
: 'module-ConversationHeader__calling-button--show'
|
||||||
)}
|
)}
|
||||||
disabled={showBackButton}
|
disabled={showBackButton}
|
||||||
>
|
>
|
||||||
{i18n('joinOngoingCall')}
|
{isNarrow ? null : i18n('joinOngoingCall')}
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
default:
|
default:
|
||||||
|
@ -342,7 +353,7 @@ export class ConversationHeader extends React.Component<PropsType> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public renderMenu(triggerId: string): JSX.Element {
|
private renderMenu(triggerId: string): ReactNode {
|
||||||
const {
|
const {
|
||||||
i18n,
|
i18n,
|
||||||
acceptedMessageRequest,
|
acceptedMessageRequest,
|
||||||
|
@ -484,7 +495,7 @@ export class ConversationHeader extends React.Component<PropsType> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderHeader(): JSX.Element {
|
private renderHeader(): ReactNode {
|
||||||
const {
|
const {
|
||||||
conversationTitle,
|
conversationTitle,
|
||||||
groupVersion,
|
groupVersion,
|
||||||
|
@ -497,92 +508,94 @@ export class ConversationHeader extends React.Component<PropsType> {
|
||||||
|
|
||||||
if (conversationTitle !== undefined) {
|
if (conversationTitle !== undefined) {
|
||||||
return (
|
return (
|
||||||
<div className="module-conversation-header__title-flex">
|
<div className="module-ConversationHeader__header">
|
||||||
<div className="module-conversation-header__title">
|
<div className="module-ConversationHeader__header__info">
|
||||||
{conversationTitle}
|
<div className="module-ConversationHeader__header__info__title">
|
||||||
|
{conversationTitle}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const hasGV2AdminEnabled =
|
let onClick: undefined | (() => void);
|
||||||
groupVersion === 2 &&
|
switch (type) {
|
||||||
window.Signal.RemoteConfig.isEnabled('desktop.gv2Admin');
|
case 'direct':
|
||||||
|
onClick = isMe
|
||||||
if (type === 'group' && hasGV2AdminEnabled) {
|
? undefined
|
||||||
const onHeaderClick = () => onShowConversationDetails();
|
: () => {
|
||||||
const onKeyDown = (e: React.KeyboardEvent): void => {
|
onShowContactModal(id);
|
||||||
if (e.key === 'Enter' || e.key === ' ') {
|
};
|
||||||
e.stopPropagation();
|
break;
|
||||||
e.preventDefault();
|
case 'group': {
|
||||||
|
const hasGV2AdminEnabled =
|
||||||
onShowConversationDetails();
|
groupVersion === 2 &&
|
||||||
}
|
window.Signal.RemoteConfig.isEnabled('desktop.gv2Admin');
|
||||||
};
|
onClick = hasGV2AdminEnabled
|
||||||
|
? () => {
|
||||||
return (
|
onShowConversationDetails();
|
||||||
<div
|
}
|
||||||
className="module-conversation-header__title-flex module-conversation-header__title-clickable"
|
: undefined;
|
||||||
onClick={onHeaderClick}
|
break;
|
||||||
onKeyDown={onKeyDown}
|
|
||||||
role="button"
|
|
||||||
tabIndex={0}
|
|
||||||
>
|
|
||||||
{this.renderAvatar()}
|
|
||||||
{this.renderTitle()}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type === 'group' || isMe) {
|
|
||||||
return (
|
|
||||||
<div className="module-conversation-header__title-flex">
|
|
||||||
{this.renderAvatar()}
|
|
||||||
{this.renderTitle()}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const onContactClick = () => onShowContactModal(id);
|
|
||||||
const onKeyDown = (e: React.KeyboardEvent): void => {
|
|
||||||
if (e.key === 'Enter' || e.key === ' ') {
|
|
||||||
e.stopPropagation();
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
onShowContactModal(id);
|
|
||||||
}
|
}
|
||||||
};
|
default:
|
||||||
|
throw missingCaseError(type);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
const contents = (
|
||||||
<div
|
<>
|
||||||
className="module-conversation-header__title-flex module-conversation-header__title-clickable"
|
|
||||||
onClick={onContactClick}
|
|
||||||
onKeyDown={onKeyDown}
|
|
||||||
role="button"
|
|
||||||
tabIndex={0}
|
|
||||||
>
|
|
||||||
{this.renderAvatar()}
|
{this.renderAvatar()}
|
||||||
{this.renderTitle()}
|
<div className="module-ConversationHeader__header__info">
|
||||||
</div>
|
{this.renderHeaderInfoTitle()}
|
||||||
|
{this.renderHeaderInfoSubtitle()}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (onClick) {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="module-ConversationHeader__header module-ConversationHeader__header--clickable"
|
||||||
|
onClick={onClick}
|
||||||
|
>
|
||||||
|
{contents}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div className="module-ConversationHeader__header">{contents}</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
public render(): JSX.Element {
|
public render(): ReactNode {
|
||||||
const { id } = this.props;
|
const { id } = this.props;
|
||||||
|
const { isNarrow } = this.state;
|
||||||
const triggerId = `conversation-${id}`;
|
const triggerId = `conversation-${id}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="module-conversation-header">
|
<Measure
|
||||||
{this.renderBackButton()}
|
bounds
|
||||||
<div className="module-conversation-header__title-container">
|
onResize={({ bounds }) => {
|
||||||
{this.renderHeader()}
|
const width = (bounds && bounds.width) || 0;
|
||||||
</div>
|
this.setState({ isNarrow: width < 500 });
|
||||||
{this.renderExpirationLength()}
|
}}
|
||||||
{this.renderOutgoingCallButtons()}
|
>
|
||||||
{this.renderSearchButton()}
|
{({ measureRef }) => (
|
||||||
{this.renderMoreButton(triggerId)}
|
<div
|
||||||
{this.renderMenu(triggerId)}
|
className={classNames('module-ConversationHeader', {
|
||||||
</div>
|
'module-ConversationHeader--narrow': isNarrow,
|
||||||
|
})}
|
||||||
|
ref={measureRef}
|
||||||
|
>
|
||||||
|
{this.renderBackButton()}
|
||||||
|
{this.renderHeader()}
|
||||||
|
{this.renderOutgoingCallButtons()}
|
||||||
|
{this.renderSearchButton()}
|
||||||
|
{this.renderMoreButton(triggerId)}
|
||||||
|
{this.renderMenu(triggerId)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Measure>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14820,7 +14820,7 @@
|
||||||
"rule": "React-createRef",
|
"rule": "React-createRef",
|
||||||
"path": "ts/components/conversation/ConversationHeader.js",
|
"path": "ts/components/conversation/ConversationHeader.js",
|
||||||
"line": " this.menuTriggerRef = react_1.default.createRef();",
|
"line": " this.menuTriggerRef = react_1.default.createRef();",
|
||||||
"lineNumber": 30,
|
"lineNumber": 32,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2020-08-28T16:12:19.904Z",
|
"updated": "2020-08-28T16:12:19.904Z",
|
||||||
"reasonDetail": "Used to reference popup menu"
|
"reasonDetail": "Used to reference popup menu"
|
||||||
|
@ -14829,7 +14829,7 @@
|
||||||
"rule": "React-createRef",
|
"rule": "React-createRef",
|
||||||
"path": "ts/components/conversation/ConversationHeader.tsx",
|
"path": "ts/components/conversation/ConversationHeader.tsx",
|
||||||
"line": " this.menuTriggerRef = React.createRef();",
|
"line": " this.menuTriggerRef = React.createRef();",
|
||||||
"lineNumber": 105,
|
"lineNumber": 112,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2020-05-20T20:10:43.540Z",
|
"updated": "2020-05-20T20:10:43.540Z",
|
||||||
"reasonDetail": "Used to reference popup menu"
|
"reasonDetail": "Used to reference popup menu"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue