mirror of
https://github.com/danog/telegram-tt.git
synced 2024-11-30 04:39:00 +01:00
[Accessibility] Composer: Support attaching media (#1752)
This commit is contained in:
parent
1aac51057d
commit
bd507a328f
@ -32,7 +32,6 @@ export { default as MobileSearch } from '../components/middle/MobileSearch';
|
||||
export { default as AttachmentModal } from '../components/middle/composer/AttachmentModal';
|
||||
export { default as PollModal } from '../components/middle/composer/PollModal';
|
||||
export { default as SymbolMenu } from '../components/middle/composer/SymbolMenu';
|
||||
export { default as AttachMenu } from '../components/middle/composer/AttachMenu';
|
||||
export { default as BotCommandTooltip } from '../components/middle/composer/BotCommandTooltip';
|
||||
export { default as BotCommandMenu } from '../components/middle/composer/BotCommandMenu';
|
||||
export { default as MentionTooltip } from '../components/middle/composer/MentionTooltip';
|
||||
|
@ -1,15 +0,0 @@
|
||||
import React, { FC, memo } from '../../../lib/teact/teact';
|
||||
import { OwnProps } from './AttachMenu';
|
||||
import { Bundles } from '../../../util/moduleLoader';
|
||||
|
||||
import useModuleLoader from '../../../hooks/useModuleLoader';
|
||||
|
||||
const AttachMenuAsync: FC<OwnProps> = (props) => {
|
||||
const { isOpen } = props;
|
||||
const AttachMenu = useModuleLoader(Bundles.Extra, 'AttachMenu', !isOpen);
|
||||
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
return AttachMenu ? <AttachMenu {...props} /> : undefined;
|
||||
};
|
||||
|
||||
export default memo(AttachMenuAsync);
|
@ -1,18 +1,31 @@
|
||||
.AttachMenu {
|
||||
position: relative;
|
||||
|
||||
.is-pointer-env & {
|
||||
> .backdrop {
|
||||
position: absolute;
|
||||
top: -1rem;
|
||||
left: auto;
|
||||
right: 0;
|
||||
width: 3.5rem;
|
||||
&--button {
|
||||
&:focus {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
}
|
||||
|
||||
&--menu {
|
||||
position: relative;
|
||||
top: -3.5rem;
|
||||
|
||||
.media-disabled > button {
|
||||
white-space: normal;
|
||||
@media (max-width: 600px) {
|
||||
top: -2.875rem;
|
||||
}
|
||||
|
||||
.is-pointer-env & {
|
||||
> .backdrop {
|
||||
position: absolute;
|
||||
top: -1rem;
|
||||
left: auto;
|
||||
right: 0;
|
||||
width: 3.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.media-disabled > button {
|
||||
white-space: normal;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,29 +1,39 @@
|
||||
import React, { FC, memo, useCallback } from '../../../lib/teact/teact';
|
||||
import React, {
|
||||
FC, memo, useCallback, useEffect,
|
||||
} from '../../../lib/teact/teact';
|
||||
|
||||
import { CONTENT_TYPES_WITH_PREVIEW } from '../../../config';
|
||||
import { IS_TOUCH_ENV } from '../../../util/environment';
|
||||
import { openSystemFilesDialog } from '../../../util/systemFilesDialog';
|
||||
import useMouseInside from '../../../hooks/useMouseInside';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import useFlag from '../../../hooks/useFlag';
|
||||
|
||||
import ResponsiveHoverButton from '../../ui/ResponsiveHoverButton';
|
||||
import Menu from '../../ui/Menu';
|
||||
import MenuItem from '../../ui/MenuItem';
|
||||
|
||||
import './AttachMenu.scss';
|
||||
|
||||
export type OwnProps = {
|
||||
isOpen: boolean;
|
||||
isButtonVisible: boolean;
|
||||
canAttachMedia: boolean;
|
||||
canAttachPolls: boolean;
|
||||
onFileSelect: (files: File[], isQuick: boolean) => void;
|
||||
onPollCreate: () => void;
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
const AttachMenu: FC<OwnProps> = ({
|
||||
isOpen, canAttachMedia, canAttachPolls, onFileSelect, onPollCreate, onClose,
|
||||
isButtonVisible, canAttachMedia, canAttachPolls, onFileSelect, onPollCreate,
|
||||
}) => {
|
||||
const [handleMouseEnter, handleMouseLeave] = useMouseInside(isOpen, onClose);
|
||||
const [isAttachMenuOpen, openAttachMenu, closeAttachMenu] = useFlag();
|
||||
const [handleMouseEnter, handleMouseLeave, markMouseInside] = useMouseInside(isAttachMenuOpen, closeAttachMenu);
|
||||
|
||||
useEffect(() => {
|
||||
if (isAttachMenuOpen) {
|
||||
markMouseInside();
|
||||
}
|
||||
}, [isAttachMenuOpen, markMouseInside]);
|
||||
|
||||
const handleFileSelect = useCallback((e: Event, isQuick: boolean) => {
|
||||
const { files } = e.target as HTMLInputElement;
|
||||
@ -46,38 +56,58 @@ const AttachMenu: FC<OwnProps> = ({
|
||||
|
||||
const lang = useLang();
|
||||
|
||||
if (!isButtonVisible) {
|
||||
return;
|
||||
}
|
||||
|
||||
return (
|
||||
<Menu
|
||||
isOpen={isOpen}
|
||||
autoClose
|
||||
positionX="right"
|
||||
positionY="bottom"
|
||||
onClose={onClose}
|
||||
className="AttachMenu fluid"
|
||||
onCloseAnimationEnd={onClose}
|
||||
onMouseEnter={!IS_TOUCH_ENV ? handleMouseEnter : undefined}
|
||||
onMouseLeave={!IS_TOUCH_ENV ? handleMouseLeave : undefined}
|
||||
noCloseOnBackdrop={!IS_TOUCH_ENV}
|
||||
>
|
||||
{/*
|
||||
<div className="AttachMenu">
|
||||
<ResponsiveHoverButton
|
||||
id="attach-menu-button"
|
||||
className={isAttachMenuOpen ? 'AttachMenu--button activated' : 'AttachMenu--button'}
|
||||
round
|
||||
color="translucent"
|
||||
onActivate={openAttachMenu}
|
||||
ariaLabel="Add an attachment"
|
||||
ariaControls="attach-menu-controls"
|
||||
hasPopup
|
||||
>
|
||||
<i className="icon-attach" />
|
||||
</ResponsiveHoverButton>
|
||||
<Menu
|
||||
id="attach-menu-controls"
|
||||
isOpen={isAttachMenuOpen}
|
||||
autoClose
|
||||
positionX="right"
|
||||
positionY="bottom"
|
||||
onClose={closeAttachMenu}
|
||||
className="AttachMenu--menu fluid"
|
||||
onCloseAnimationEnd={closeAttachMenu}
|
||||
onMouseEnter={!IS_TOUCH_ENV ? handleMouseEnter : undefined}
|
||||
onMouseLeave={!IS_TOUCH_ENV ? handleMouseLeave : undefined}
|
||||
noCloseOnBackdrop={!IS_TOUCH_ENV}
|
||||
ariaLabelledBy="attach-menu-button"
|
||||
>
|
||||
{/*
|
||||
** Using ternary operator here causes some attributes from first clause
|
||||
** transferring to the fragment content in the second clause
|
||||
*/}
|
||||
{!canAttachMedia && (
|
||||
<MenuItem className="media-disabled" disabled>Posting media content is not allowed in this group.</MenuItem>
|
||||
)}
|
||||
{canAttachMedia && (
|
||||
<>
|
||||
<MenuItem icon="photo" onClick={handleQuickSelect}>
|
||||
{lang('AttachmentMenu.PhotoOrVideo')}
|
||||
</MenuItem>
|
||||
<MenuItem icon="document" onClick={handleDocumentSelect}>{lang('AttachDocument')}</MenuItem>
|
||||
</>
|
||||
)}
|
||||
{canAttachPolls && (
|
||||
<MenuItem icon="poll" onClick={onPollCreate}>{lang('Poll')}</MenuItem>
|
||||
)}
|
||||
</Menu>
|
||||
{!canAttachMedia && (
|
||||
<MenuItem className="media-disabled" disabled>Posting media content is not allowed in this group.</MenuItem>
|
||||
)}
|
||||
{canAttachMedia && (
|
||||
<>
|
||||
<MenuItem icon="photo" onClick={handleQuickSelect}>
|
||||
{lang('AttachmentMenu.PhotoOrVideo')}
|
||||
</MenuItem>
|
||||
<MenuItem icon="document" onClick={handleDocumentSelect}>{lang('AttachDocument')}</MenuItem>
|
||||
</>
|
||||
)}
|
||||
{canAttachPolls && (
|
||||
<MenuItem icon="poll" onClick={onPollCreate}>{lang('Poll')}</MenuItem>
|
||||
)}
|
||||
</Menu>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -250,6 +250,7 @@
|
||||
margin-right: -0.5rem;
|
||||
}
|
||||
|
||||
> .AttachMenu > .Button,
|
||||
> .Button {
|
||||
flex-shrink: 0;
|
||||
background: none !important;
|
||||
@ -263,7 +264,7 @@
|
||||
color: var(--color-composer-button);
|
||||
}
|
||||
|
||||
+ .Button {
|
||||
+ .Button, + .AttachMenu {
|
||||
margin-left: -1rem;
|
||||
}
|
||||
|
||||
@ -271,7 +272,7 @@
|
||||
width: 2.875rem;
|
||||
height: 2.875rem;
|
||||
|
||||
+ .Button {
|
||||
+ .Button, + .AttachMenu {
|
||||
margin-left: -0.6875rem;
|
||||
}
|
||||
}
|
||||
|
@ -84,8 +84,8 @@ import Button from '../../ui/Button';
|
||||
import ResponsiveHoverButton from '../../ui/ResponsiveHoverButton';
|
||||
import Spinner from '../../ui/Spinner';
|
||||
import CalendarModal from '../../common/CalendarModal.async';
|
||||
import AttachMenu from './AttachMenu';
|
||||
import Avatar from '../../common/Avatar';
|
||||
import AttachMenu from './AttachMenu.async';
|
||||
import SymbolMenu from './SymbolMenu.async';
|
||||
import InlineBotTooltip from './InlineBotTooltip.async';
|
||||
import MentionTooltip from './MentionTooltip.async';
|
||||
@ -289,7 +289,6 @@ const Composer: FC<OwnProps & StateProps> = ({
|
||||
|
||||
const [isBotKeyboardOpen, openBotKeyboard, closeBotKeyboard] = useFlag();
|
||||
const [isBotCommandMenuOpen, openBotCommandMenu, closeBotCommandMenu] = useFlag();
|
||||
const [isAttachMenuOpen, openAttachMenu, closeAttachMenu] = useFlag();
|
||||
const [isSymbolMenuOpen, openSymbolMenu, closeSymbolMenu] = useFlag();
|
||||
const [isSendAsMenuOpen, openSendAsMenu, closeSendAsMenu] = useFlag();
|
||||
const [isDeleteModalOpen, openDeleteModal, closeDeleteModal] = useFlag();
|
||||
@ -1014,17 +1013,6 @@ const Composer: FC<OwnProps & StateProps> = ({
|
||||
<i className="icon-bot-command" />
|
||||
</ResponsiveHoverButton>
|
||||
)}
|
||||
{!activeVoiceRecording && !editingMessage && (
|
||||
<ResponsiveHoverButton
|
||||
className={isAttachMenuOpen ? 'activated' : ''}
|
||||
round
|
||||
color="translucent"
|
||||
onActivate={openAttachMenu}
|
||||
ariaLabel="Add an attachment"
|
||||
>
|
||||
<i className="icon-attach" />
|
||||
</ResponsiveHoverButton>
|
||||
)}
|
||||
{activeVoiceRecording && currentRecordTime && (
|
||||
<span className="recording-state">
|
||||
{formatVoiceRecordDuration(currentRecordTime - startRecordTimeRef.current!)}
|
||||
@ -1044,12 +1032,11 @@ const Composer: FC<OwnProps & StateProps> = ({
|
||||
addRecentEmoji={addRecentEmoji}
|
||||
/>
|
||||
<AttachMenu
|
||||
isOpen={isAttachMenuOpen}
|
||||
isButtonVisible={!activeVoiceRecording && !editingMessage}
|
||||
canAttachMedia={canAttachMedia}
|
||||
canAttachPolls={canAttachPolls}
|
||||
onFileSelect={handleFileSelect}
|
||||
onPollCreate={openPollModal}
|
||||
onClose={closeAttachMenu}
|
||||
/>
|
||||
{botKeyboardMessageId && (
|
||||
<BotKeyboardMenu
|
||||
|
@ -402,6 +402,7 @@ const MessageInput: FC<OwnProps & StateProps> = ({
|
||||
onMouseDown={handleMouseDown}
|
||||
onContextMenu={IS_ANDROID ? stopEvent : undefined}
|
||||
onTouchCancel={IS_ANDROID ? processSelection : undefined}
|
||||
aria-label={placeholder}
|
||||
/>
|
||||
<div ref={cloneRef} className={buildClassName(className, 'clone')} dir="auto" />
|
||||
{!forcedPlaceholder && <span className="placeholder-text" dir="auto">{placeholder}</span>}
|
||||
|
@ -20,6 +20,7 @@ export type OwnProps = {
|
||||
'primary' | 'secondary' | 'gray' | 'danger' | 'translucent' | 'translucent-white' | 'translucent-black' | 'dark'
|
||||
);
|
||||
backgroundImage?: string;
|
||||
id?: string;
|
||||
className?: string;
|
||||
round?: boolean;
|
||||
pill?: boolean;
|
||||
@ -27,6 +28,8 @@ export type OwnProps = {
|
||||
isText?: boolean;
|
||||
isLoading?: boolean;
|
||||
ariaLabel?: string;
|
||||
ariaControls?: string;
|
||||
hasPopup?: boolean;
|
||||
href?: string;
|
||||
download?: string;
|
||||
disabled?: boolean;
|
||||
@ -49,6 +52,7 @@ const CLICKED_TIMEOUT = 400;
|
||||
const Button: FC<OwnProps> = ({
|
||||
ref,
|
||||
type = 'button',
|
||||
id,
|
||||
onClick,
|
||||
onContextMenu,
|
||||
onMouseDown,
|
||||
@ -66,6 +70,8 @@ const Button: FC<OwnProps> = ({
|
||||
isText,
|
||||
isLoading,
|
||||
ariaLabel,
|
||||
ariaControls,
|
||||
hasPopup,
|
||||
href,
|
||||
download,
|
||||
disabled,
|
||||
@ -122,12 +128,16 @@ const Button: FC<OwnProps> = ({
|
||||
return (
|
||||
<a
|
||||
ref={elementRef as RefObject<HTMLAnchorElement>}
|
||||
id={id}
|
||||
className={fullClassName}
|
||||
href={href}
|
||||
title={ariaLabel}
|
||||
download={download}
|
||||
tabIndex={tabIndex}
|
||||
dir={isRtl ? 'rtl' : undefined}
|
||||
aria-label={ariaLabel}
|
||||
aria-controls={ariaControls}
|
||||
aria-haspopup={hasPopup}
|
||||
>
|
||||
{children}
|
||||
{!disabled && ripple && (
|
||||
@ -141,6 +151,7 @@ const Button: FC<OwnProps> = ({
|
||||
// eslint-disable-next-line react/button-has-type
|
||||
<button
|
||||
ref={elementRef as RefObject<HTMLButtonElement>}
|
||||
id={id}
|
||||
type={type}
|
||||
className={fullClassName}
|
||||
onClick={handleClick}
|
||||
@ -150,6 +161,8 @@ const Button: FC<OwnProps> = ({
|
||||
onMouseLeave={onMouseLeave && !disabled ? onMouseLeave : undefined}
|
||||
onFocus={onFocus && !disabled ? onFocus : undefined}
|
||||
aria-label={ariaLabel}
|
||||
aria-controls={ariaControls}
|
||||
aria-haspopup={hasPopup}
|
||||
title={ariaLabel}
|
||||
tabIndex={tabIndex}
|
||||
dir={isRtl ? 'rtl' : undefined}
|
||||
|
@ -82,6 +82,7 @@ const InputText: FC<OwnProps> = ({
|
||||
onKeyDown={onKeyDown}
|
||||
onBlur={onBlur}
|
||||
onPaste={onPaste}
|
||||
aria-label={labelText}
|
||||
/>
|
||||
{labelText && (
|
||||
<label htmlFor={id}>{labelText}</label>
|
||||
|
@ -18,9 +18,11 @@ type OwnProps = {
|
||||
ref?: RefObject<HTMLDivElement>;
|
||||
containerRef?: RefObject<HTMLElement>;
|
||||
isOpen: boolean;
|
||||
id?: string;
|
||||
className?: string;
|
||||
style?: string;
|
||||
bubbleStyle?: string;
|
||||
ariaLabelledBy?: string;
|
||||
transformOriginX?: number;
|
||||
transformOriginY?: number;
|
||||
positionX?: 'left' | 'right';
|
||||
@ -44,9 +46,11 @@ const Menu: FC<OwnProps> = ({
|
||||
ref,
|
||||
containerRef,
|
||||
isOpen,
|
||||
id,
|
||||
className,
|
||||
style,
|
||||
bubbleStyle,
|
||||
ariaLabelledBy,
|
||||
children,
|
||||
transformOriginX,
|
||||
transformOriginY,
|
||||
@ -113,16 +117,19 @@ const Menu: FC<OwnProps> = ({
|
||||
|
||||
return (
|
||||
<div
|
||||
id={id}
|
||||
className={buildClassName(
|
||||
'Menu no-selection',
|
||||
!noCompact && IS_COMPACT_MENU && 'compact',
|
||||
!IS_BACKDROP_BLUR_SUPPORTED && 'no-blur',
|
||||
className,
|
||||
)}
|
||||
style={style}
|
||||
aria-labelledby={ariaLabelledBy}
|
||||
role={ariaLabelledBy ? 'menu' : undefined}
|
||||
onKeyDown={isOpen ? handleKeyDown : undefined}
|
||||
onMouseEnter={onMouseEnter}
|
||||
onMouseLeave={isOpen ? onMouseLeave : undefined}
|
||||
style={style}
|
||||
>
|
||||
{isOpen && (
|
||||
// This only prevents click events triggering on underlying elements
|
||||
|
@ -52,7 +52,7 @@ const ResponsiveHoverButton: FC<OwnProps> = ({ onActivate, ...buttonProps }) =>
|
||||
{...buttonProps}
|
||||
onMouseEnter={!IS_TOUCH_ENV ? handleMouseEnter : undefined}
|
||||
onMouseLeave={!IS_TOUCH_ENV ? handleMouseLeave : undefined}
|
||||
onClick={IS_TOUCH_ENV ? onActivate : undefined}
|
||||
onClick={onActivate}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user