Fix minor UI issues with composer

This commit is contained in:
Fedor Indutny 2022-06-15 10:53:08 -07:00 committed by GitHub
parent 3b2000a0ba
commit 4b8cb9f040
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 172 additions and 114 deletions

View File

@ -6178,6 +6178,7 @@ button.module-image__border-overlay:focus {
.module-sticker-button__button { .module-sticker-button__button {
border: 0; border: 0;
border-radius: 4px;
background: none; background: none;
width: 32px; width: 32px;
height: 32px; height: 32px;
@ -6187,7 +6188,7 @@ button.module-image__border-overlay:focus {
@include keyboard-mode { @include keyboard-mode {
&:focus { &:focus {
outline: 1px solid $color-ultramarine; outline: 2px solid $color-ultramarine;
} }
} }
@ -6682,6 +6683,7 @@ button.module-image__border-overlay:focus {
.module-emoji-button__button { .module-emoji-button__button {
border: 0; border: 0;
border-radius: 4px;
background: none; background: none;
width: 32px; width: 32px;
height: 32px; height: 32px;
@ -6691,7 +6693,7 @@ button.module-image__border-overlay:focus {
@include keyboard-mode { @include keyboard-mode {
&:focus { &:focus {
outline: 1px solid $color-ultramarine; outline: 2px solid $color-ultramarine;
} }
} }
@ -6829,6 +6831,8 @@ button.module-image__border-overlay:focus {
} }
&__input { &__input {
$border-size: 1px;
border-radius: 18px; border-radius: 18px;
overflow: hidden; overflow: hidden;
word-break: break-word; word-break: break-word;
@ -6836,8 +6840,6 @@ button.module-image__border-overlay:focus {
// Override Quill styles // Override Quill styles
.ql-container { .ql-container {
@include font-body-1; @include font-body-1;
line-height: 21px;
font-size: 15px;
} }
.ql-blank::before { .ql-blank::before {
@ -6863,9 +6865,11 @@ button.module-image__border-overlay:focus {
} }
&__scroller { &__scroller {
padding: 5px 11px 6px 11px; $padding-top: 5px;
min-height: 32px; padding: $padding-top 10px $padding-top 10px;
max-height: 80px;
min-height: calc(32px - 2 * $border-size);
max-height: calc(72px - 2 * $border-size);
overflow: auto; overflow: auto;
&::-webkit-scrollbar-thumb { &::-webkit-scrollbar-thumb {
@ -6878,22 +6882,21 @@ button.module-image__border-overlay:focus {
} }
&--large { &--large {
max-height: 227px; max-height: calc(212px - 2 * $border-size);
min-height: 227px; min-height: calc(212px - 2 * $border-size);
.DraftEditor-root { .DraftEditor-root {
height: 227px - 2 * 7px; // subtract padding height: calc(212px - 2 * $padding-top - 2 * $border-size);
} }
} }
} }
&:focus-within { border: $border-size solid transparent;
@include light-theme() {
outline: 1px solid $color-ultramarine;
}
@include dark-theme() { &:focus-within {
outline: 1px solid $color-ultramarine; outline: 0;
@include keyboard-mode {
border: $border-size solid $color-ultramarine;
} }
} }
} }
@ -6925,7 +6928,7 @@ button.module-image__border-overlay:focus {
padding: 0 12px; padding: 0 12px;
display: flex; display: flex;
flex-direction: row; flex-direction: row;
align-items: center; align-items: end;
justify-content: flex-start; justify-content: flex-start;
background: none; background: none;
border: none; border: none;

View File

@ -12,6 +12,7 @@
&__microphone { &__microphone {
height: 32px; height: 32px;
width: 32px; width: 32px;
border-radius: 4px;
text-align: center; text-align: center;
background: none; background: none;
display: flex; display: flex;
@ -23,7 +24,7 @@
@include keyboard-mode { @include keyboard-mode {
&:focus { &:focus {
outline: 1px solid $color-ultramarine; outline: 2px solid $color-ultramarine;
} }
} }

View File

@ -4,7 +4,7 @@
.CompositionArea { .CompositionArea {
position: relative; position: relative;
min-height: 42px; min-height: 42px;
padding-top: 6px; padding: 10px 0 10px 0;
&__placeholder { &__placeholder {
flex-grow: 1; flex-grow: 1;
@ -16,7 +16,7 @@
&__row { &__row {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
align-items: center; align-items: end;
&--center { &--center {
justify-content: center; justify-content: center;
@ -25,7 +25,7 @@
padding: 0 12px; padding: 0 12px;
} }
&--control-row { &--control-row {
margin-top: 8px; margin-top: 12px;
} }
&--column { &--column {
flex-direction: column; flex-direction: column;
@ -82,8 +82,8 @@
position: absolute; position: absolute;
left: calc(50% - $width / 2); left: calc(50% - $width / 2);
// 6px coming from padding-top, 1px from outline // 6px coming from padding-top of .module-composition-input__input__scroller
top: calc(0px - $height / 2 - 6px - 1px); top: calc(0px - $height / 2 - 6px);
border-radius: 12px 12px 0 0; border-radius: 12px 12px 0 0;
pointer-events: none; pointer-events: none;
@ -194,6 +194,7 @@
&__attach-file { &__attach-file {
width: 32px; width: 32px;
height: 32px; height: 32px;
border-radius: 4px;
padding: 0; padding: 0;
border: none; border: none;
background: transparent; background: transparent;
@ -203,7 +204,7 @@
@include keyboard-mode { @include keyboard-mode {
&:focus { &:focus {
outline: 1px solid $color-ultramarine; outline: 2px solid $color-ultramarine;
} }
} }

View File

@ -272,10 +272,7 @@
} }
&--audio { &--audio {
@include normal-button( @include normal-button('../images/icons/v2/phone-outline-24.svg', 24px);
'../images/icons/v2/phone-right-outline-24.svg',
24px
);
} }
&--search { &--search {

View File

@ -35,22 +35,12 @@
} }
&__composition-area { &__composition-area {
margin-bottom: 6px;
// We need to use the wrapper because the conversation view calculates the height of all // We need to use the wrapper because the conversation view calculates the height of all
// things in the composition area. A margin on an inner div won't be included in that // things in the composition area. A margin on an inner div won't be included in that
// height calculation. // height calculation.
.quote-wrapper { .quote-wrapper,
margin-left: 18px;
margin-right: 18px;
margin-top: 3px;
}
.preview-wrapper { .preview-wrapper {
margin-top: 3px; margin: 0 16px 10px 16px;
margin-left: 12px;
margin-right: 12px;
margin-bottom: 2px;
} }
} }
} }

View File

@ -16,18 +16,20 @@
&__button { &__button {
@include button-reset(); @include button-reset();
align-items: center; align-items: center;
border-radius: 16px; border-radius: 4px;
display: flex; display: flex;
height: 32px; height: 32px;
justify-content: center; justify-content: center;
opacity: 0.5;
width: 32px; width: 32px;
&:focus, @include keyboard-mode {
&:hover { &:focus {
opacity: 1; outline: 2px solid $color-ultramarine;
}
} }
outline: none;
&::after { &::after {
content: ''; content: '';
display: block; display: block;

View File

@ -3,11 +3,13 @@
.module-quote { .module-quote {
&__container { &__container {
margin: { .module-message & {
left: -6px; margin: {
right: -6px; left: -6px;
top: 3px; right: -6px;
bottom: 5px; top: 3px;
bottom: 5px;
}
} }
} }
@ -91,17 +93,30 @@
border-left-color: $color-white; border-left-color: $color-white;
} }
} }
.module-quote--compose-#{$color} {
background-color: scale-color($value, $lightness: 60%);
border-left-color: $value;
@include dark-theme {
background-color: scale-color($value, $lightness: -40%);
border-left-color: $color-white;
}
}
} }
.module-quote--compose-custom,
.module-quote--incoming-custom, .module-quote--incoming-custom,
.module-quote--outgoing-custom { .module-quote--outgoing-custom {
background-attachment: fixed; background-attachment: fixed;
} }
@each $color, $value in $conversation-colors-gradient { @each $color, $value in $conversation-colors-gradient {
.module-quote--compose-#{$color},
.module-quote--incoming-#{$color} { .module-quote--incoming-#{$color} {
border-left-color: map-get($value, 'start'); border-left-color: map-get($value, 'start');
} }
.module-quote--compose-#{$color},
.module-quote--incoming-#{$color}, .module-quote--incoming-#{$color},
.module-quote--outgoing-#{$color} { .module-quote--outgoing-#{$color} {
background-attachment: fixed; background-attachment: fixed;

View File

@ -77,7 +77,9 @@
} }
&:focus { &:focus {
border: solid 1px $color-ultramarine; @include keyboard-mode {
border: solid 1px $color-ultramarine;
}
outline: none; outline: none;
} }

View File

@ -618,6 +618,7 @@ export const CompositionArea = ({
{quotedMessageProps && ( {quotedMessageProps && (
<div className="quote-wrapper"> <div className="quote-wrapper">
<Quote <Quote
isCompose
{...quotedMessageProps} {...quotedMessageProps}
i18n={i18n} i18n={i18n}
onClick={onClickQuotedMessage} onClick={onClickQuotedMessage}

View File

@ -188,7 +188,6 @@ export function ContextMenu<T>({
} }
}; };
// We use regular MouseEvent below, and this one uses React.MouseEvent
const handleClick = (ev: KeyboardEvent | React.MouseEvent) => { const handleClick = (ev: KeyboardEvent | React.MouseEvent) => {
setMenuShowing(true); setMenuShowing(true);
ev.stopPropagation(); ev.stopPropagation();

View File

@ -40,6 +40,8 @@ type StateType = {
}; };
export class MainHeader extends React.Component<PropsType, StateType> { export class MainHeader extends React.Component<PropsType, StateType> {
public containerRef: React.RefObject<HTMLDivElement> = React.createRef();
constructor(props: PropsType) { constructor(props: PropsType) {
super(props); super(props);
@ -55,18 +57,14 @@ export class MainHeader extends React.Component<PropsType, StateType> {
if ( if (
showingAvatarPopup && showingAvatarPopup &&
popperRoot && popperRoot &&
!popperRoot.contains(target as Node) !popperRoot.contains(target as Node) &&
!this.containerRef.current?.contains(target as Node)
) { ) {
this.hideAvatarPopup(); this.hideAvatarPopup();
} }
}; };
public showAvatarPopup = ( public showAvatarPopup = (): void => {
event: React.MouseEvent<HTMLButtonElement, MouseEvent>
): void => {
event.preventDefault();
event.stopPropagation();
const popperRoot = document.createElement('div'); const popperRoot = document.createElement('div');
document.body.appendChild(popperRoot); document.body.appendChild(popperRoot);
@ -142,7 +140,10 @@ export class MainHeader extends React.Component<PropsType, StateType> {
<Manager> <Manager>
<Reference> <Reference>
{({ ref }) => ( {({ ref }) => (
<div className="module-main-header__avatar--container"> <div
className="module-main-header__avatar--container"
ref={this.containerRef}
>
<Avatar <Avatar
acceptedMessageRequest acceptedMessageRequest
avatarPath={avatarPath} avatarPath={avatarPath}

View File

@ -8,6 +8,7 @@ import { createPortal } from 'react-dom';
import classNames from 'classnames'; import classNames from 'classnames';
import { Manager, Popper, Reference } from 'react-popper'; import { Manager, Popper, Reference } from 'react-popper';
import type { LocalizerType } from '../types/Util'; import type { LocalizerType } from '../types/Util';
import { useRefMerger } from '../hooks/useRefMerger';
export type PropsType = { export type PropsType = {
i18n: LocalizerType; i18n: LocalizerType;
@ -26,11 +27,11 @@ export const MediaQualitySelector = ({
undefined undefined
); );
// We use regular MouseEvent below, and this one uses React.MouseEvent const buttonRef = React.useRef<HTMLButtonElement | null>(null);
const handleClick = (ev: KeyboardEvent | React.MouseEvent) => { const refMerger = useRefMerger();
const handleClick = () => {
setMenuShowing(true); setMenuShowing(true);
ev.stopPropagation();
ev.preventDefault();
}; };
const handleKeyDown = (ev: KeyboardEvent) => { const handleKeyDown = (ev: KeyboardEvent) => {
@ -66,7 +67,10 @@ export const MediaQualitySelector = ({
setPopperRoot(root); setPopperRoot(root);
document.body.appendChild(root); document.body.appendChild(root);
const handleOutsideClick = (event: MouseEvent) => { const handleOutsideClick = (event: MouseEvent) => {
if (!root.contains(event.target as Node)) { if (
!root.contains(event.target as Node) &&
event.target !== buttonRef.current
) {
handleClose(); handleClose();
event.stopPropagation(); event.stopPropagation();
event.preventDefault(); event.preventDefault();
@ -97,7 +101,7 @@ export const MediaQualitySelector = ({
})} })}
onClick={handleClick} onClick={handleClick}
onKeyDown={handleKeyDown} onKeyDown={handleKeyDown}
ref={ref} ref={refMerger(buttonRef, ref)}
type="button" type="button"
/> />
)} )}

View File

@ -31,6 +31,7 @@ export type Props = {
i18n: LocalizerType; i18n: LocalizerType;
isFromMe: boolean; isFromMe: boolean;
isIncoming?: boolean; isIncoming?: boolean;
isCompose?: boolean;
isStoryReply?: boolean; isStoryReply?: boolean;
moduleClassName?: string; moduleClassName?: string;
onClick?: () => void; onClick?: () => void;
@ -505,6 +506,7 @@ export class Quote extends React.Component<Props, State> {
const { const {
conversationColor, conversationColor,
customColor, customColor,
isCompose,
isIncoming, isIncoming,
onClick, onClick,
rawAttachment, rawAttachment,
@ -516,6 +518,16 @@ export class Quote extends React.Component<Props, State> {
return null; return null;
} }
let directionClassName: string;
if (isCompose) {
directionClassName = this.getClassName('--compose');
} else if (isIncoming) {
directionClassName = this.getClassName('--incoming');
} else {
directionClassName = this.getClassName('--outgoing');
}
const colorClassName = `${directionClassName}-${conversationColor}`;
return ( return (
<div className={this.getClassName('__container')}> <div className={this.getClassName('__container')}>
<button <button
@ -524,12 +536,8 @@ export class Quote extends React.Component<Props, State> {
onKeyDown={this.handleKeyDown} onKeyDown={this.handleKeyDown}
className={classNames( className={classNames(
this.getClassName(''), this.getClassName(''),
isIncoming directionClassName,
? this.getClassName('--incoming') colorClassName,
: this.getClassName('--outgoing'),
isIncoming
? this.getClassName(`--incoming-${conversationColor}`)
: this.getClassName(`--outgoing-${conversationColor}`),
!onClick && this.getClassName('--no-click'), !onClick && this.getClassName('--no-click'),
referencedMessageNotFound && referencedMessageNotFound &&
this.getClassName('--with-reference-warning') this.getClassName('--with-reference-warning')

View File

@ -10,6 +10,7 @@ import { Emoji } from './Emoji';
import type { Props as EmojiPickerProps } from './EmojiPicker'; import type { Props as EmojiPickerProps } from './EmojiPicker';
import { EmojiPicker } from './EmojiPicker'; import { EmojiPicker } from './EmojiPicker';
import type { LocalizerType } from '../../types/Util'; import type { LocalizerType } from '../../types/Util';
import { useRefMerger } from '../../hooks/useRefMerger';
import * as KeyboardLayout from '../../services/keyboardLayout'; import * as KeyboardLayout from '../../services/keyboardLayout';
export type OwnProps = { export type OwnProps = {
@ -43,19 +44,16 @@ export const EmojiButton = React.memo(
const [popperRoot, setPopperRoot] = React.useState<HTMLElement | null>( const [popperRoot, setPopperRoot] = React.useState<HTMLElement | null>(
null null
); );
const buttonRef = React.useRef<HTMLButtonElement | null>(null);
const refMerger = useRefMerger();
const handleClickButton = React.useCallback( const handleClickButton = React.useCallback(() => {
(event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => { if (popperRoot) {
event.preventDefault(); setOpen(false);
event.stopPropagation(); } else {
if (popperRoot) { setOpen(true);
setOpen(false); }
} else { }, [popperRoot, setOpen]);
setOpen(true);
}
},
[popperRoot, setOpen]
);
const handleClose = React.useCallback(() => { const handleClose = React.useCallback(() => {
setOpen(false); setOpen(false);
@ -71,7 +69,10 @@ export const EmojiButton = React.memo(
setPopperRoot(root); setPopperRoot(root);
document.body.appendChild(root); document.body.appendChild(root);
const handleOutsideClick = (event: MouseEvent) => { const handleOutsideClick = (event: MouseEvent) => {
if (!root.contains(event.target as Node)) { if (
!root.contains(event.target as Node) &&
event.target !== buttonRef.current
) {
handleClose(); handleClose();
event.stopPropagation(); event.stopPropagation();
event.preventDefault(); event.preventDefault();
@ -124,7 +125,7 @@ export const EmojiButton = React.memo(
{({ ref }) => ( {({ ref }) => (
<button <button
type="button" type="button"
ref={ref} ref={refMerger(buttonRef, ref)}
onClick={handleClickButton} onClick={handleClickButton}
className={classNames(className, { className={classNames(className, {
'module-emoji-button__button': true, 'module-emoji-button__button': true,

View File

@ -15,6 +15,7 @@ import { countStickers } from './lib';
import { offsetDistanceModifier } from '../../util/popperUtil'; import { offsetDistanceModifier } from '../../util/popperUtil';
import { themeClassName } from '../../util/theme'; import { themeClassName } from '../../util/theme';
import * as KeyboardLayout from '../../services/keyboardLayout'; import * as KeyboardLayout from '../../services/keyboardLayout';
import { useRefMerger } from '../../hooks/useRefMerger';
export type OwnProps = { export type OwnProps = {
readonly className?: string; readonly className?: string;
@ -66,34 +67,30 @@ export const StickerButton = React.memo(
const [popperRoot, setPopperRoot] = React.useState<HTMLElement | null>( const [popperRoot, setPopperRoot] = React.useState<HTMLElement | null>(
null null
); );
const buttonRef = React.useRef<HTMLButtonElement | null>(null);
const refMerger = useRefMerger();
const handleClickButton = React.useCallback( const handleClickButton = React.useCallback(() => {
(event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => { // Clear tooltip state
event.preventDefault(); clearInstalledStickerPack();
event.stopPropagation(); clearShowIntroduction();
// Clear tooltip state // Handle button click
clearInstalledStickerPack(); if (installedPacks.length === 0) {
clearShowIntroduction(); onClickAddPack?.();
} else if (popperRoot) {
// Handle button click setOpen(false);
if (installedPacks.length === 0) { } else {
onClickAddPack?.(); setOpen(true);
} else if (popperRoot) { }
setOpen(false); }, [
} else { clearInstalledStickerPack,
setOpen(true); clearShowIntroduction,
} installedPacks,
}, onClickAddPack,
[ popperRoot,
clearInstalledStickerPack, setOpen,
clearShowIntroduction, ]);
installedPacks,
onClickAddPack,
popperRoot,
setOpen,
]
);
const handlePickSticker = React.useCallback( const handlePickSticker = React.useCallback(
(packId: string, stickerId: number, url: string) => { (packId: string, stickerId: number, url: string) => {
@ -139,7 +136,11 @@ export const StickerButton = React.memo(
targetClassName.indexOf('module-sticker-picker__header__button') < targetClassName.indexOf('module-sticker-picker__header__button') <
0; 0;
if (!root.contains(targetElement) && isMissingButtonClass) { if (
!root.contains(targetElement) &&
isMissingButtonClass &&
targetElement !== buttonRef.current
) {
setOpen(false); setOpen(false);
} }
}; };
@ -214,7 +215,7 @@ export const StickerButton = React.memo(
{({ ref }) => ( {({ ref }) => (
<button <button
type="button" type="button"
ref={ref} ref={refMerger(buttonRef, ref)}
onClick={handleClickButton} onClick={handleClickButton}
className={classNames( className={classNames(
{ {

View File

@ -8687,6 +8687,22 @@
"reasonCategory": "usageTrusted", "reasonCategory": "usageTrusted",
"updated": "2021-10-11T21:21:08.188Z" "updated": "2021-10-11T21:21:08.188Z"
}, },
{
"rule": "React-createRef",
"path": "ts/components/MainHeader.tsx",
"line": " public containerRef: React.RefObject<HTMLDivElement> = React.createRef();",
"reasonCategory": "usageTrusted",
"updated": "2022-06-14T22:04:43.988Z",
"reasonDetail": "Handling outside click"
},
{
"rule": "React-useRef",
"path": "ts/components/MediaQualitySelector.tsx",
"line": " const buttonRef = React.useRef<HTMLButtonElement | null>(null);",
"reasonCategory": "usageTrusted",
"updated": "2022-06-14T22:04:43.988Z",
"reasonDetail": "Handling outside click"
},
{ {
"rule": "React-useRef", "rule": "React-useRef",
"path": "ts/components/Modal.tsx", "path": "ts/components/Modal.tsx",
@ -8899,6 +8915,14 @@
"updated": "2019-11-01T22:46:33.013Z", "updated": "2019-11-01T22:46:33.013Z",
"reasonDetail": "Used for setting focus only" "reasonDetail": "Used for setting focus only"
}, },
{
"rule": "React-useRef",
"path": "ts/components/emoji/EmojiButton.tsx",
"line": " const buttonRef = React.useRef<HTMLButtonElement | null>(null);",
"reasonCategory": "usageTrusted",
"updated": "2022-06-14T22:04:43.988Z",
"reasonDetail": "Handling outside click"
},
{ {
"rule": "React-useRef", "rule": "React-useRef",
"path": "ts/components/installScreen/InstallScreenChoosingDeviceNameStep.tsx", "path": "ts/components/installScreen/InstallScreenChoosingDeviceNameStep.tsx",
@ -8907,6 +8931,14 @@
"updated": "2021-12-06T23:07:28.947Z", "updated": "2021-12-06T23:07:28.947Z",
"reasonDetail": "Doesn't touch the DOM." "reasonDetail": "Doesn't touch the DOM."
}, },
{
"rule": "React-useRef",
"path": "ts/components/stickers/StickerButton.tsx",
"line": " const buttonRef = React.useRef<HTMLButtonElement | null>(null);",
"reasonCategory": "usageTrusted",
"updated": "2022-06-14T22:04:43.988Z",
"reasonDetail": "Handling outside click"
},
{ {
"rule": "React-useRef", "rule": "React-useRef",
"path": "ts/hooks/useIntersectionObserver.ts", "path": "ts/hooks/useIntersectionObserver.ts",