Introduce focus traps for ModalHost, add button role to DropZone
This commit is contained in:
parent
adaeb81c32
commit
48229332ea
8 changed files with 371 additions and 294 deletions
|
@ -1003,6 +1003,30 @@ Signal Desktop makes use of the following open source projects.
|
|||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
## focus-trap-react
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 David Clark
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
## form-data
|
||||
|
||||
Copyright (c) 2012 Felix Geisendörfer (felix@debuggable.com) and contributors
|
||||
|
|
|
@ -96,6 +96,7 @@
|
|||
"fast-glob": "3.2.1",
|
||||
"filesize": "3.6.1",
|
||||
"firstline": "1.2.1",
|
||||
"focus-trap-react": "8.8.1",
|
||||
"form-data": "3.0.0",
|
||||
"fs-extra": "5.0.0",
|
||||
"fuse.js": "3.4.4",
|
||||
|
|
|
@ -47,7 +47,10 @@ export const DropZone: React.ComponentType<Props> = props => {
|
|||
}, [isDragActive, onDragActive]);
|
||||
|
||||
return (
|
||||
<div {...getRootProps({ className: getClassName(props, isDragActive) })}>
|
||||
<div
|
||||
{...getRootProps({ className: getClassName(props, isDragActive) })}
|
||||
role="button"
|
||||
>
|
||||
<input {...getInputProps()} />
|
||||
<svg viewBox="0 0 36 36" width="36px" height="36px">
|
||||
<path d="M32 17.25H18.75V4h-1.5v13.25H4v1.5h13.25V32h1.5V18.75H32v-1.5z" />
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
import React, { useEffect } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { createPortal } from 'react-dom';
|
||||
import FocusTrap from 'focus-trap-react';
|
||||
|
||||
import { Theme, themeClassName } from '../util/theme';
|
||||
import { useEscapeHandling } from '../hooks/useEscapeHandling';
|
||||
|
||||
|
@ -56,17 +58,19 @@ export const ModalHost = React.memo(
|
|||
|
||||
return root
|
||||
? createPortal(
|
||||
<div
|
||||
role="presentation"
|
||||
className={classNames(
|
||||
'module-modal-host__overlay',
|
||||
theme ? themeClassName(theme) : undefined
|
||||
)}
|
||||
onMouseDown={noMouseClose ? undefined : handleMouseDown}
|
||||
onMouseUp={noMouseClose ? undefined : handleMouseUp}
|
||||
>
|
||||
{children}
|
||||
</div>,
|
||||
<FocusTrap>
|
||||
<div
|
||||
role="presentation"
|
||||
className={classNames(
|
||||
'module-modal-host__overlay',
|
||||
theme ? themeClassName(theme) : undefined
|
||||
)}
|
||||
onMouseDown={noMouseClose ? undefined : handleMouseDown}
|
||||
onMouseUp={noMouseClose ? undefined : handleMouseUp}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</FocusTrap>,
|
||||
root
|
||||
)
|
||||
: null;
|
||||
|
|
|
@ -18,6 +18,8 @@ import {
|
|||
last,
|
||||
zipObject,
|
||||
} from 'lodash';
|
||||
import FocusTrap from 'focus-trap-react';
|
||||
|
||||
import { Emoji } from './Emoji';
|
||||
import { dataByCategory, search } from './lib';
|
||||
import { LocalizerType } from '../../types/Util';
|
||||
|
@ -301,124 +303,126 @@ export const EmojiPicker = React.memo(
|
|||
);
|
||||
|
||||
return (
|
||||
<div className="module-emoji-picker" ref={ref} style={style}>
|
||||
<header className="module-emoji-picker__header">
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleToggleSearch}
|
||||
title={i18n('EmojiPicker--search-placeholder')}
|
||||
className={classNames(
|
||||
'module-emoji-picker__button',
|
||||
'module-emoji-picker__button--icon',
|
||||
searchMode
|
||||
? 'module-emoji-picker__button--icon--close'
|
||||
: 'module-emoji-picker__button--icon--search'
|
||||
<FocusTrap>
|
||||
<div className="module-emoji-picker" ref={ref} style={style}>
|
||||
<header className="module-emoji-picker__header">
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleToggleSearch}
|
||||
title={i18n('EmojiPicker--search-placeholder')}
|
||||
className={classNames(
|
||||
'module-emoji-picker__button',
|
||||
'module-emoji-picker__button--icon',
|
||||
searchMode
|
||||
? 'module-emoji-picker__button--icon--close'
|
||||
: 'module-emoji-picker__button--icon--search'
|
||||
)}
|
||||
aria-label={i18n('EmojiPicker--search-placeholder')}
|
||||
/>
|
||||
{searchMode ? (
|
||||
<div className="module-emoji-picker__header__search-field">
|
||||
<input
|
||||
ref={focusOnRender}
|
||||
className="module-emoji-picker__header__search-field__input"
|
||||
placeholder={i18n('EmojiPicker--search-placeholder')}
|
||||
onChange={handleSearchChange}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
categories.map(cat =>
|
||||
cat === 'recents' && firstRecent.length === 0 ? null : (
|
||||
<button
|
||||
type="button"
|
||||
key={cat}
|
||||
data-category={cat}
|
||||
title={cat}
|
||||
onClick={handleSelectCategory}
|
||||
className={classNames(
|
||||
'module-emoji-picker__button',
|
||||
'module-emoji-picker__button--icon',
|
||||
`module-emoji-picker__button--icon--${cat}`,
|
||||
selectedCategory === cat
|
||||
? 'module-emoji-picker__button--selected'
|
||||
: null
|
||||
)}
|
||||
aria-label={i18n(`EmojiPicker__button--${cat}`)}
|
||||
/>
|
||||
)
|
||||
)
|
||||
)}
|
||||
aria-label={i18n('EmojiPicker--search-placeholder')}
|
||||
/>
|
||||
{searchMode ? (
|
||||
<div className="module-emoji-picker__header__search-field">
|
||||
<input
|
||||
ref={focusOnRender}
|
||||
className="module-emoji-picker__header__search-field__input"
|
||||
placeholder={i18n('EmojiPicker--search-placeholder')}
|
||||
onChange={handleSearchChange}
|
||||
/>
|
||||
</header>
|
||||
{emojiGrid.length > 0 ? (
|
||||
<div>
|
||||
<AutoSizer>
|
||||
{({ width, height }) => (
|
||||
<Grid
|
||||
key={searchText}
|
||||
className="module-emoji-picker__body"
|
||||
width={width}
|
||||
height={height}
|
||||
columnCount={COL_COUNT}
|
||||
columnWidth={38}
|
||||
rowHeight={getRowHeight}
|
||||
rowCount={emojiGrid.length}
|
||||
cellRenderer={cellRenderer}
|
||||
scrollToRow={scrollToRow}
|
||||
scrollToAlignment="start"
|
||||
onSectionRendered={onSectionRendered}
|
||||
/>
|
||||
)}
|
||||
</AutoSizer>
|
||||
</div>
|
||||
) : (
|
||||
categories.map(cat =>
|
||||
cat === 'recents' && firstRecent.length === 0 ? null : (
|
||||
<div
|
||||
className={classNames(
|
||||
'module-emoji-picker__body',
|
||||
'module-emoji-picker__body--empty'
|
||||
)}
|
||||
>
|
||||
{i18n('EmojiPicker--empty')}
|
||||
<Emoji
|
||||
shortName="slightly_frowning_face"
|
||||
size={16}
|
||||
style={{ marginLeft: '4px' }}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<footer className="module-emoji-picker__footer">
|
||||
{Boolean(onClickSettings) && (
|
||||
<button
|
||||
aria-label={i18n('CustomizingPreferredReactions__title')}
|
||||
className="module-emoji-picker__button module-emoji-picker__button--footer module-emoji-picker__button--settings"
|
||||
onClick={onClickSettings}
|
||||
title={i18n('CustomizingPreferredReactions__title')}
|
||||
type="button"
|
||||
/>
|
||||
)}
|
||||
<div className="module-emoji-picker__footer__skin-tones">
|
||||
{[0, 1, 2, 3, 4, 5].map(tone => (
|
||||
<button
|
||||
type="button"
|
||||
key={cat}
|
||||
data-category={cat}
|
||||
title={cat}
|
||||
onClick={handleSelectCategory}
|
||||
key={tone}
|
||||
data-tone={tone}
|
||||
onClick={handlePickTone}
|
||||
title={i18n('EmojiPicker--skin-tone', [`${tone}`])}
|
||||
className={classNames(
|
||||
'module-emoji-picker__button',
|
||||
'module-emoji-picker__button--icon',
|
||||
`module-emoji-picker__button--icon--${cat}`,
|
||||
selectedCategory === cat
|
||||
'module-emoji-picker__button--footer',
|
||||
selectedTone === tone
|
||||
? 'module-emoji-picker__button--selected'
|
||||
: null
|
||||
)}
|
||||
aria-label={i18n(`EmojiPicker__button--${cat}`)}
|
||||
/>
|
||||
)
|
||||
)
|
||||
)}
|
||||
</header>
|
||||
{emojiGrid.length > 0 ? (
|
||||
<div>
|
||||
<AutoSizer>
|
||||
{({ width, height }) => (
|
||||
<Grid
|
||||
key={searchText}
|
||||
className="module-emoji-picker__body"
|
||||
width={width}
|
||||
height={height}
|
||||
columnCount={COL_COUNT}
|
||||
columnWidth={38}
|
||||
rowHeight={getRowHeight}
|
||||
rowCount={emojiGrid.length}
|
||||
cellRenderer={cellRenderer}
|
||||
scrollToRow={scrollToRow}
|
||||
scrollToAlignment="start"
|
||||
onSectionRendered={onSectionRendered}
|
||||
/>
|
||||
)}
|
||||
</AutoSizer>
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
className={classNames(
|
||||
'module-emoji-picker__body',
|
||||
'module-emoji-picker__body--empty'
|
||||
>
|
||||
<Emoji shortName="hand" skinTone={tone} size={20} />
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
{Boolean(onClickSettings) && (
|
||||
<div className="module-emoji-picker__footer__settings-spacer" />
|
||||
)}
|
||||
>
|
||||
{i18n('EmojiPicker--empty')}
|
||||
<Emoji
|
||||
shortName="slightly_frowning_face"
|
||||
size={16}
|
||||
style={{ marginLeft: '4px' }}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<footer className="module-emoji-picker__footer">
|
||||
{Boolean(onClickSettings) && (
|
||||
<button
|
||||
aria-label={i18n('CustomizingPreferredReactions__title')}
|
||||
className="module-emoji-picker__button module-emoji-picker__button--footer module-emoji-picker__button--settings"
|
||||
onClick={onClickSettings}
|
||||
title={i18n('CustomizingPreferredReactions__title')}
|
||||
type="button"
|
||||
/>
|
||||
)}
|
||||
<div className="module-emoji-picker__footer__skin-tones">
|
||||
{[0, 1, 2, 3, 4, 5].map(tone => (
|
||||
<button
|
||||
type="button"
|
||||
key={tone}
|
||||
data-tone={tone}
|
||||
onClick={handlePickTone}
|
||||
title={i18n('EmojiPicker--skin-tone', [`${tone}`])}
|
||||
className={classNames(
|
||||
'module-emoji-picker__button',
|
||||
'module-emoji-picker__button--footer',
|
||||
selectedTone === tone
|
||||
? 'module-emoji-picker__button--selected'
|
||||
: null
|
||||
)}
|
||||
>
|
||||
<Emoji shortName="hand" skinTone={tone} size={20} />
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
{Boolean(onClickSettings) && (
|
||||
<div className="module-emoji-picker__footer__settings-spacer" />
|
||||
)}
|
||||
</footer>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</FocusTrap>
|
||||
);
|
||||
}
|
||||
)
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
|
||||
import * as React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import FocusTrap from 'focus-trap-react';
|
||||
|
||||
import { useRestoreFocus } from '../../hooks/useRestoreFocus';
|
||||
import { StickerPackType, StickerType } from '../../state/ducks/stickers';
|
||||
import { LocalizerType } from '../../types/Util';
|
||||
|
@ -146,187 +148,193 @@ export const StickerPicker = React.memo(
|
|||
const showLongText = showPickerHint;
|
||||
|
||||
return (
|
||||
<div className="module-sticker-picker" ref={ref} style={style}>
|
||||
<div className="module-sticker-picker__header">
|
||||
<div className="module-sticker-picker__header__packs">
|
||||
<div
|
||||
className="module-sticker-picker__header__packs__slider"
|
||||
style={{
|
||||
transform: `translateX(-${getPacksPageOffset(
|
||||
packsPage,
|
||||
packs.length
|
||||
)}px)`,
|
||||
}}
|
||||
>
|
||||
{hasPacks ? (
|
||||
<button
|
||||
type="button"
|
||||
onClick={recentsHandler}
|
||||
className={classNames({
|
||||
'module-sticker-picker__header__button': true,
|
||||
'module-sticker-picker__header__button--recents': true,
|
||||
'module-sticker-picker__header__button--selected':
|
||||
currentTab === 'recents',
|
||||
})}
|
||||
aria-label={i18n('stickers--StickerPicker--Recents')}
|
||||
/>
|
||||
) : null}
|
||||
{packs.map((pack, i) => (
|
||||
<button
|
||||
type="button"
|
||||
key={pack.id}
|
||||
onClick={packsHandlers[i]}
|
||||
className={classNames(
|
||||
'module-sticker-picker__header__button',
|
||||
{
|
||||
'module-sticker-picker__header__button--selected':
|
||||
currentTab === pack.id,
|
||||
'module-sticker-picker__header__button--error':
|
||||
pack.status === 'error',
|
||||
}
|
||||
)}
|
||||
>
|
||||
{pack.cover ? (
|
||||
<img
|
||||
className="module-sticker-picker__header__button__image"
|
||||
src={pack.cover.url}
|
||||
alt={pack.title}
|
||||
title={pack.title}
|
||||
/>
|
||||
) : (
|
||||
<div className="module-sticker-picker__header__button__image-placeholder" />
|
||||
)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
{!isUsingKeyboard && packsPage > 0 ? (
|
||||
<button
|
||||
type="button"
|
||||
className={classNames(
|
||||
'module-sticker-picker__header__button',
|
||||
'module-sticker-picker__header__button--prev-page'
|
||||
)}
|
||||
onClick={onClickPrevPackPage}
|
||||
aria-label={i18n('stickers--StickerPicker--PrevPage')}
|
||||
/>
|
||||
) : null}
|
||||
{!isUsingKeyboard && !isLastPacksPage(packsPage, packs.length) ? (
|
||||
<button
|
||||
type="button"
|
||||
className={classNames(
|
||||
'module-sticker-picker__header__button',
|
||||
'module-sticker-picker__header__button--next-page'
|
||||
)}
|
||||
onClick={onClickNextPackPage}
|
||||
aria-label={i18n('stickers--StickerPicker--NextPage')}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
ref={addPackRef}
|
||||
className={classNames(
|
||||
'module-sticker-picker__header__button',
|
||||
'module-sticker-picker__header__button--add-pack',
|
||||
{
|
||||
'module-sticker-picker__header__button--hint': showPickerHint,
|
||||
}
|
||||
)}
|
||||
onClick={onClickAddPack}
|
||||
aria-label={i18n('stickers--StickerPicker--AddPack')}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className={classNames('module-sticker-picker__body', {
|
||||
'module-sticker-picker__body--empty': isEmpty,
|
||||
})}
|
||||
>
|
||||
{showPickerHint ? (
|
||||
<div
|
||||
className={classNames(
|
||||
'module-sticker-picker__body__text',
|
||||
'module-sticker-picker__body__text--hint',
|
||||
{
|
||||
'module-sticker-picker__body__text--pin': showEmptyText,
|
||||
}
|
||||
)}
|
||||
>
|
||||
{i18n('stickers--StickerPicker--Hint')}
|
||||
</div>
|
||||
) : null}
|
||||
{!hasPacks ? (
|
||||
<div className="module-sticker-picker__body__text">
|
||||
{i18n('stickers--StickerPicker--NoPacks')}
|
||||
</div>
|
||||
) : null}
|
||||
{pendingCount > 0 ? (
|
||||
<div className="module-sticker-picker__body__text">
|
||||
{i18n('stickers--StickerPicker--DownloadPending')}
|
||||
</div>
|
||||
) : null}
|
||||
{downloadError ? (
|
||||
<div
|
||||
className={classNames(
|
||||
'module-sticker-picker__body__text',
|
||||
'module-sticker-picker__body__text--error'
|
||||
)}
|
||||
>
|
||||
{stickers.length > 0
|
||||
? i18n('stickers--StickerPicker--DownloadError')
|
||||
: i18n('stickers--StickerPicker--Empty')}
|
||||
</div>
|
||||
) : null}
|
||||
{hasPacks && showEmptyText ? (
|
||||
<div
|
||||
className={classNames('module-sticker-picker__body__text', {
|
||||
'module-sticker-picker__body__text--error': !isRecents,
|
||||
})}
|
||||
>
|
||||
{isRecents
|
||||
? i18n('stickers--StickerPicker--NoRecents')
|
||||
: i18n('stickers--StickerPicker--Empty')}
|
||||
</div>
|
||||
) : null}
|
||||
{!isEmpty ? (
|
||||
<div
|
||||
className={classNames('module-sticker-picker__body__content', {
|
||||
'module-sticker-picker__body__content--under-text': showText,
|
||||
'module-sticker-picker__body__content--under-long-text': showLongText,
|
||||
})}
|
||||
>
|
||||
{stickers.map(({ packId, id, url }, index: number) => {
|
||||
const maybeFocusRef = index === 0 ? focusRef : undefined;
|
||||
|
||||
return (
|
||||
<FocusTrap>
|
||||
<div className="module-sticker-picker" ref={ref} style={style}>
|
||||
<div className="module-sticker-picker__header">
|
||||
<div className="module-sticker-picker__header__packs">
|
||||
<div
|
||||
className="module-sticker-picker__header__packs__slider"
|
||||
style={{
|
||||
transform: `translateX(-${getPacksPageOffset(
|
||||
packsPage,
|
||||
packs.length
|
||||
)}px)`,
|
||||
}}
|
||||
>
|
||||
{hasPacks ? (
|
||||
<button
|
||||
type="button"
|
||||
ref={maybeFocusRef}
|
||||
key={`${packId}-${id}`}
|
||||
className="module-sticker-picker__body__cell"
|
||||
onClick={() => onPickSticker(packId, id)}
|
||||
>
|
||||
<img
|
||||
className="module-sticker-picker__body__cell__image"
|
||||
src={url}
|
||||
alt={packTitle}
|
||||
/>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
{Array(pendingCount)
|
||||
.fill(0)
|
||||
.map((_, i) => (
|
||||
<div
|
||||
// eslint-disable-next-line react/no-array-index-key
|
||||
key={i}
|
||||
className="module-sticker-picker__body__cell__placeholder"
|
||||
role="presentation"
|
||||
onClick={recentsHandler}
|
||||
className={classNames({
|
||||
'module-sticker-picker__header__button': true,
|
||||
'module-sticker-picker__header__button--recents': true,
|
||||
'module-sticker-picker__header__button--selected':
|
||||
currentTab === 'recents',
|
||||
})}
|
||||
aria-label={i18n('stickers--StickerPicker--Recents')}
|
||||
/>
|
||||
) : null}
|
||||
{packs.map((pack, i) => (
|
||||
<button
|
||||
type="button"
|
||||
key={pack.id}
|
||||
onClick={packsHandlers[i]}
|
||||
className={classNames(
|
||||
'module-sticker-picker__header__button',
|
||||
{
|
||||
'module-sticker-picker__header__button--selected':
|
||||
currentTab === pack.id,
|
||||
'module-sticker-picker__header__button--error':
|
||||
pack.status === 'error',
|
||||
}
|
||||
)}
|
||||
>
|
||||
{pack.cover ? (
|
||||
<img
|
||||
className="module-sticker-picker__header__button__image"
|
||||
src={pack.cover.url}
|
||||
alt={pack.title}
|
||||
title={pack.title}
|
||||
/>
|
||||
) : (
|
||||
<div className="module-sticker-picker__header__button__image-placeholder" />
|
||||
)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
{!isUsingKeyboard && packsPage > 0 ? (
|
||||
<button
|
||||
type="button"
|
||||
className={classNames(
|
||||
'module-sticker-picker__header__button',
|
||||
'module-sticker-picker__header__button--prev-page'
|
||||
)}
|
||||
onClick={onClickPrevPackPage}
|
||||
aria-label={i18n('stickers--StickerPicker--PrevPage')}
|
||||
/>
|
||||
) : null}
|
||||
{!isUsingKeyboard &&
|
||||
!isLastPacksPage(packsPage, packs.length) ? (
|
||||
<button
|
||||
type="button"
|
||||
className={classNames(
|
||||
'module-sticker-picker__header__button',
|
||||
'module-sticker-picker__header__button--next-page'
|
||||
)}
|
||||
onClick={onClickNextPackPage}
|
||||
aria-label={i18n('stickers--StickerPicker--NextPage')}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
<button
|
||||
type="button"
|
||||
ref={addPackRef}
|
||||
className={classNames(
|
||||
'module-sticker-picker__header__button',
|
||||
'module-sticker-picker__header__button--add-pack',
|
||||
{
|
||||
'module-sticker-picker__header__button--hint': showPickerHint,
|
||||
}
|
||||
)}
|
||||
onClick={onClickAddPack}
|
||||
aria-label={i18n('stickers--StickerPicker--AddPack')}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className={classNames('module-sticker-picker__body', {
|
||||
'module-sticker-picker__body--empty': isEmpty,
|
||||
})}
|
||||
>
|
||||
{showPickerHint ? (
|
||||
<div
|
||||
className={classNames(
|
||||
'module-sticker-picker__body__text',
|
||||
'module-sticker-picker__body__text--hint',
|
||||
{
|
||||
'module-sticker-picker__body__text--pin': showEmptyText,
|
||||
}
|
||||
)}
|
||||
>
|
||||
{i18n('stickers--StickerPicker--Hint')}
|
||||
</div>
|
||||
) : null}
|
||||
{!hasPacks ? (
|
||||
<div className="module-sticker-picker__body__text">
|
||||
{i18n('stickers--StickerPicker--NoPacks')}
|
||||
</div>
|
||||
) : null}
|
||||
{pendingCount > 0 ? (
|
||||
<div className="module-sticker-picker__body__text">
|
||||
{i18n('stickers--StickerPicker--DownloadPending')}
|
||||
</div>
|
||||
) : null}
|
||||
{downloadError ? (
|
||||
<div
|
||||
className={classNames(
|
||||
'module-sticker-picker__body__text',
|
||||
'module-sticker-picker__body__text--error'
|
||||
)}
|
||||
>
|
||||
{stickers.length > 0
|
||||
? i18n('stickers--StickerPicker--DownloadError')
|
||||
: i18n('stickers--StickerPicker--Empty')}
|
||||
</div>
|
||||
) : null}
|
||||
{hasPacks && showEmptyText ? (
|
||||
<div
|
||||
className={classNames('module-sticker-picker__body__text', {
|
||||
'module-sticker-picker__body__text--error': !isRecents,
|
||||
})}
|
||||
>
|
||||
{isRecents
|
||||
? i18n('stickers--StickerPicker--NoRecents')
|
||||
: i18n('stickers--StickerPicker--Empty')}
|
||||
</div>
|
||||
) : null}
|
||||
{!isEmpty ? (
|
||||
<div
|
||||
className={classNames(
|
||||
'module-sticker-picker__body__content',
|
||||
{
|
||||
'module-sticker-picker__body__content--under-text': showText,
|
||||
'module-sticker-picker__body__content--under-long-text': showLongText,
|
||||
}
|
||||
)}
|
||||
>
|
||||
{stickers.map(({ packId, id, url }, index: number) => {
|
||||
const maybeFocusRef = index === 0 ? focusRef : undefined;
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
ref={maybeFocusRef}
|
||||
key={`${packId}-${id}`}
|
||||
className="module-sticker-picker__body__cell"
|
||||
onClick={() => onPickSticker(packId, id)}
|
||||
>
|
||||
<img
|
||||
className="module-sticker-picker__body__cell__image"
|
||||
src={url}
|
||||
alt={packTitle}
|
||||
/>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
{Array(pendingCount)
|
||||
.fill(0)
|
||||
.map((_, i) => (
|
||||
<div
|
||||
// eslint-disable-next-line react/no-array-index-key
|
||||
key={i}
|
||||
className="module-sticker-picker__body__cell__placeholder"
|
||||
role="presentation"
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</FocusTrap>
|
||||
);
|
||||
}
|
||||
)
|
||||
|
|
|
@ -1688,6 +1688,20 @@
|
|||
"reasonCategory": "falseMatch",
|
||||
"updated": "2018-09-15T00:38:04.183Z"
|
||||
},
|
||||
{
|
||||
"rule": "React-findDOMNode",
|
||||
"path": "node_modules/focus-trap-react/dist/focus-trap-react.js",
|
||||
"line": " var focusTrapElementDOMNodes = this.focusTrapElements.map( // NOTE: `findDOMNode()` does not support CSS selectors; it'll just return",
|
||||
"reasonCategory": "exampleCode",
|
||||
"updated": "2021-10-01T23:53:26.107Z"
|
||||
},
|
||||
{
|
||||
"rule": "React-findDOMNode",
|
||||
"path": "node_modules/focus-trap-react/src/focus-trap-react.js",
|
||||
"line": " // NOTE: `findDOMNode()` does not support CSS selectors; it'll just return",
|
||||
"reasonCategory": "exampleCode",
|
||||
"updated": "2021-10-01T23:53:26.107Z"
|
||||
},
|
||||
{
|
||||
"rule": "jQuery-load(",
|
||||
"path": "node_modules/foreground-child/node_modules/signal-exit/index.js",
|
||||
|
|
19
yarn.lock
19
yarn.lock
|
@ -8677,6 +8677,20 @@ focus-lock@^0.6.3:
|
|||
resolved "https://registry.yarnpkg.com/focus-lock/-/focus-lock-0.6.5.tgz#f6eb37832a9b1b205406175f5277396a28c0fce1"
|
||||
integrity sha512-i/mVBOoa9o+tl+u9owOJUF8k8L85odZNIsctB+JAK2HFT8jckiBwmk+3uydlm6FN8czgnkIwQtBv6yyAbrzXjw==
|
||||
|
||||
focus-trap-react@8.8.1:
|
||||
version "8.8.1"
|
||||
resolved "https://registry.yarnpkg.com/focus-trap-react/-/focus-trap-react-8.8.1.tgz#2c7043e748460a191a1c7e2552e7c964f3f9fd46"
|
||||
integrity sha512-Uy7U/l3fozlwLYBSQHP91QjuRUUcAQ9FJ3glAGmwF/fXSiPa/4negTy02zWElLZdZDPIfebC8V14aI1Gzc5V3w==
|
||||
dependencies:
|
||||
focus-trap "^6.7.1"
|
||||
|
||||
focus-trap@^6.7.1:
|
||||
version "6.7.1"
|
||||
resolved "https://registry.yarnpkg.com/focus-trap/-/focus-trap-6.7.1.tgz#d474f86dbaf3c7fbf0d53cf0b12295f4f4068d10"
|
||||
integrity sha512-a6czHbT9twVpy2RpkWQA9vIgwQgB9Nx1PIxNNUxQT4nugG/3QibwxO+tWTh9i+zSY2SFiX4pnYhTaFaQF/6ZAg==
|
||||
dependencies:
|
||||
tabbable "^5.2.1"
|
||||
|
||||
follow-redirects@^1.0.0:
|
||||
version "1.13.0"
|
||||
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.0.tgz#b42e8d93a2a7eea5ed88633676d6597bc8e384db"
|
||||
|
@ -17445,6 +17459,11 @@ symbol.prototype.description@^1.0.0:
|
|||
dependencies:
|
||||
has-symbols "^1.0.0"
|
||||
|
||||
tabbable@^5.2.1:
|
||||
version "5.2.1"
|
||||
resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-5.2.1.tgz#e3fda7367ddbb172dcda9f871c0fdb36d1c4cd9c"
|
||||
integrity sha512-40pEZ2mhjaZzK0BnI+QGNjJO8UYx9pP5v7BGe17SORTO0OEuuaAwQTkAp8whcZvqon44wKFOikD+Al11K3JICQ==
|
||||
|
||||
table@^5.2.3:
|
||||
version "5.4.6"
|
||||
resolved "https://registry.yarnpkg.com/table/-/table-5.4.6.tgz#1292d19500ce3f86053b05f0e8e7e4a3bb21079e"
|
||||
|
|
Loading…
Reference in a new issue