Merge branch 'accessible-emoji-picker' into 'develop'

Keyboard-accessible emoji picker

See merge request soapbox-pub/soapbox-fe!634
This commit is contained in:
Alex Gleason 2021-07-21 16:41:26 +00:00
commit b22f20a390
14 changed files with 298 additions and 68 deletions

View File

@ -2,55 +2,80 @@
exports[`<EmojiSelector /> renders correctly 1`] = ` exports[`<EmojiSelector /> renders correctly 1`] = `
<div <div
className="emoji-react-selector" onBlur={[Function]}
onFocus={[Function]}
tabIndex="-1"
> >
<button <div
className="emoji-react-selector__emoji" className="emoji-react-selector"
dangerouslySetInnerHTML={ onBlur={[Function]}
Object { >
"__html": "<img draggable=\\"false\\" class=\\"emojione\\" alt=\\"👍\\" title=\\":+1:\\" src=\\"/emoji/1f44d.svg\\" />", <button
className="emoji-react-selector__emoji"
dangerouslySetInnerHTML={
Object {
"__html": "<img draggable=\\"false\\" class=\\"emojione\\" alt=\\"👍\\" title=\\":+1:\\" src=\\"/emoji/1f44d.svg\\" />",
}
} }
} onClick={[Function]}
/> onKeyUp={[Function]}
<button tabIndex={-1}
className="emoji-react-selector__emoji" />
dangerouslySetInnerHTML={ <button
Object { className="emoji-react-selector__emoji"
"__html": "<img draggable=\\"false\\" class=\\"emojione\\" alt=\\"❤\\" title=\\":heart:\\" src=\\"/emoji/2764.svg\\" />", dangerouslySetInnerHTML={
Object {
"__html": "<img draggable=\\"false\\" class=\\"emojione\\" alt=\\"❤\\" title=\\":heart:\\" src=\\"/emoji/2764.svg\\" />",
}
} }
} onClick={[Function]}
/> onKeyUp={[Function]}
<button tabIndex={-1}
className="emoji-react-selector__emoji" />
dangerouslySetInnerHTML={ <button
Object { className="emoji-react-selector__emoji"
"__html": "<img draggable=\\"false\\" class=\\"emojione\\" alt=\\"😆\\" title=\\":laughing:\\" src=\\"/emoji/1f606.svg\\" />", dangerouslySetInnerHTML={
Object {
"__html": "<img draggable=\\"false\\" class=\\"emojione\\" alt=\\"😆\\" title=\\":laughing:\\" src=\\"/emoji/1f606.svg\\" />",
}
} }
} onClick={[Function]}
/> onKeyUp={[Function]}
<button tabIndex={-1}
className="emoji-react-selector__emoji" />
dangerouslySetInnerHTML={ <button
Object { className="emoji-react-selector__emoji"
"__html": "<img draggable=\\"false\\" class=\\"emojione\\" alt=\\"😮\\" title=\\":open_mouth:\\" src=\\"/emoji/1f62e.svg\\" />", dangerouslySetInnerHTML={
Object {
"__html": "<img draggable=\\"false\\" class=\\"emojione\\" alt=\\"😮\\" title=\\":open_mouth:\\" src=\\"/emoji/1f62e.svg\\" />",
}
} }
} onClick={[Function]}
/> onKeyUp={[Function]}
<button tabIndex={-1}
className="emoji-react-selector__emoji" />
dangerouslySetInnerHTML={ <button
Object { className="emoji-react-selector__emoji"
"__html": "<img draggable=\\"false\\" class=\\"emojione\\" alt=\\"😢\\" title=\\":cry:\\" src=\\"/emoji/1f622.svg\\" />", dangerouslySetInnerHTML={
Object {
"__html": "<img draggable=\\"false\\" class=\\"emojione\\" alt=\\"😢\\" title=\\":cry:\\" src=\\"/emoji/1f622.svg\\" />",
}
} }
} onClick={[Function]}
/> onKeyUp={[Function]}
<button tabIndex={-1}
className="emoji-react-selector__emoji" />
dangerouslySetInnerHTML={ <button
Object { className="emoji-react-selector__emoji"
"__html": "<img draggable=\\"false\\" class=\\"emojione\\" alt=\\"😩\\" title=\\":weary:\\" src=\\"/emoji/1f629.svg\\" />", dangerouslySetInnerHTML={
Object {
"__html": "<img draggable=\\"false\\" class=\\"emojione\\" alt=\\"😩\\" title=\\":weary:\\" src=\\"/emoji/1f629.svg\\" />",
}
} }
} onClick={[Function]}
/> onKeyUp={[Function]}
tabIndex={-1}
/>
</div>
</div> </div>
`; `;

View File

@ -4,7 +4,10 @@ import EmojiSelector from '../emoji_selector';
describe('<EmojiSelector />', () => { describe('<EmojiSelector />', () => {
it('renders correctly', () => { it('renders correctly', () => {
const component = createComponent(<EmojiSelector />); const children = <EmojiSelector />;
children.__proto__.addEventListener = () => {};
const component = createComponent(children, {}, true);
const tree = component.toJSON(); const tree = component.toJSON();
expect(tree).toMatchSnapshot(); expect(tree).toMatchSnapshot();
}); });

View File

@ -1,5 +1,6 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { HotKeys } from 'react-hotkeys';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import emojify from 'soapbox/features/emoji/emoji'; import emojify from 'soapbox/features/emoji/emoji';
@ -15,28 +16,89 @@ class EmojiSelector extends ImmutablePureComponent {
static propTypes = { static propTypes = {
onReact: PropTypes.func.isRequired, onReact: PropTypes.func.isRequired,
onUnfocus: PropTypes.func,
visible: PropTypes.bool, visible: PropTypes.bool,
focused: PropTypes.bool,
} }
static defaultProps = { static defaultProps = {
onReact: () => {}, onReact: () => {},
onUnfocus: () => {},
visible: false, visible: false,
} }
handleBlur = e => {
const { focused, onUnfocus } = this.props;
if (focused && (!e.relatedTarget || !e.relatedTarget.classList.contains('emoji-react-selector__emoji'))) {
onUnfocus();
}
}
handleKeyUp = i => e => {
const { onUnfocus } = this.props;
switch (e.key) {
case 'Left':
case 'ArrowLeft':
if (i !== 0) {
this.node.querySelector(`.emoji-react-selector__emoji:nth-child(${i})`).focus();
}
break;
case 'Right':
case 'ArrowRight':
if (i !== this.props.allowedEmoji.size - 1) {
this.node.querySelector(`.emoji-react-selector__emoji:nth-child(${i + 2})`).focus();
}
break;
case 'Escape':
onUnfocus();
break;
}
}
handleReact = emoji => () => {
const { onReact, focused, onUnfocus } = this.props;
onReact(emoji)();
if (focused) {
onUnfocus();
}
}
handlers = {
open: () => {},
};
setRef = c => {
this.node = c;
}
render() { render() {
const { onReact, visible, allowedEmoji } = this.props; const { visible, focused, allowedEmoji } = this.props;
return ( return (
<div className={classNames('emoji-react-selector', { 'emoji-react-selector--visible': visible })}> <HotKeys
{allowedEmoji.map((emoji, i) => ( handlers={this.handlers}
<button >
key={i} <div
className='emoji-react-selector__emoji' className={classNames('emoji-react-selector', { 'emoji-react-selector--visible': visible, 'emoji-react-selector--focused': focused })}
dangerouslySetInnerHTML={{ __html: emojify(emoji) }} onBlur={this.handleBlur}
onClick={onReact(emoji)} ref={this.setRef}
/> >
))} {allowedEmoji.map((emoji, i) => (
</div> <button
key={i}
className='emoji-react-selector__emoji'
dangerouslySetInnerHTML={{ __html: emojify(emoji) }}
onClick={this.handleReact(emoji)}
onKeyUp={this.handleKeyUp(i, emoji)}
tabIndex={(visible || focused) ? 0 : -1}
/>
))}
</div>
</HotKeys>
); );
} }

View File

@ -108,6 +108,7 @@ class Status extends ImmutablePureComponent {
state = { state = {
showMedia: defaultMediaVisibility(this.props.status, this.props.displayMedia), showMedia: defaultMediaVisibility(this.props.status, this.props.displayMedia),
statusId: undefined, statusId: undefined,
emojiSelectorFocused: false,
}; };
// Track height changes we know about to compensate scrolling // Track height changes we know about to compensate scrolling
@ -255,6 +256,27 @@ class Status extends ImmutablePureComponent {
this.handleToggleMediaVisibility(); this.handleToggleMediaVisibility();
} }
handleHotkeyReact = () => {
this._expandEmojiSelector();
}
handleEmojiSelectorExpand = e => {
if (e.key === 'Enter') {
this._expandEmojiSelector();
}
e.preventDefault();
}
handleEmojiSelectorUnfocus = () => {
this.setState({ emojiSelectorFocused: false });
}
_expandEmojiSelector = () => {
this.setState({ emojiSelectorFocused: true });
const firstEmoji = this.node.querySelector('.emoji-react-selector .emoji-react-selector__emoji');
firstEmoji.focus();
};
_properStatus() { _properStatus() {
const { status } = this.props; const { status } = this.props;
@ -278,6 +300,7 @@ class Status extends ImmutablePureComponent {
let { status, account, ...other } = this.props; let { status, account, ...other } = this.props;
if (status === null) { if (status === null) {
return null; return null;
} }
@ -443,6 +466,7 @@ class Status extends ImmutablePureComponent {
moveDown: this.handleHotkeyMoveDown, moveDown: this.handleHotkeyMoveDown,
toggleHidden: this.handleHotkeyToggleHidden, toggleHidden: this.handleHotkeyToggleHidden,
toggleSensitive: this.handleHotkeyToggleSensitive, toggleSensitive: this.handleHotkeyToggleSensitive,
react: this.handleHotkeyReact,
}; };
const statusUrl = `/@${status.getIn(['account', 'acct'])}/posts/${status.get('id')}`; const statusUrl = `/@${status.getIn(['account', 'acct'])}/posts/${status.get('id')}`;
@ -506,7 +530,13 @@ class Status extends ImmutablePureComponent {
</button> </button>
)} )}
<StatusActionBar status={status} account={account} {...other} /> <StatusActionBar
status={status}
account={account}
emojiSelectorFocused={this.state.emojiSelectorFocused}
handleEmojiSelectorUnfocus={this.handleEmojiSelectorUnfocus}
{...other}
/>
</div> </div>
</div> </div>
</HotKeys> </HotKeys>

View File

@ -91,6 +91,10 @@ class StatusActionBar extends ImmutablePureComponent {
isStaff: PropTypes.bool.isRequired, isStaff: PropTypes.bool.isRequired,
isAdmin: PropTypes.bool.isRequired, isAdmin: PropTypes.bool.isRequired,
allowedEmoji: ImmutablePropTypes.list, allowedEmoji: ImmutablePropTypes.list,
emojiSelectorFocused: PropTypes.bool,
handleEmojiSelectorUnfocus: PropTypes.func.isRequired,
emojiSelectorFocused: PropTypes.bool,
handleEmojiSelectorUnfocus: PropTypes.func.isRequired,
}; };
static defaultProps = { static defaultProps = {
@ -106,6 +110,7 @@ class StatusActionBar extends ImmutablePureComponent {
updateOnProps = [ updateOnProps = [
'status', 'status',
'withDismiss', 'withDismiss',
'emojiSelectorFocused',
] ]
handleReplyClick = () => { handleReplyClick = () => {
@ -359,7 +364,7 @@ class StatusActionBar extends ImmutablePureComponent {
} }
render() { render() {
const { status, intl, allowedEmoji } = this.props; const { status, intl, allowedEmoji, emojiSelectorFocused, handleEmojiSelectorUnfocus } = this.props;
const { emojiSelectorVisible } = this.state; const { emojiSelectorVisible } = this.state;
const publicStatus = ['public', 'unlisted'].includes(status.get('visibility')); const publicStatus = ['public', 'unlisted'].includes(status.get('visibility'));
@ -422,7 +427,12 @@ class StatusActionBar extends ImmutablePureComponent {
onMouseLeave={this.handleLikeButtonLeave} onMouseLeave={this.handleLikeButtonLeave}
ref={this.setRef} ref={this.setRef}
> >
<EmojiSelector onReact={this.handleReactClick} visible={emojiSelectorVisible} /> <EmojiSelector
onReact={this.handleReactClick}
visible={emojiSelectorVisible}
focused={emojiSelectorFocused}
onUnfocus={handleEmojiSelectorUnfocus}
/>
<IconButton <IconButton
className='status__action-bar-button star-icon' className='status__action-bar-button star-icon'
animate animate

View File

@ -46,9 +46,9 @@ export default class StatusList extends ImmutablePureComponent {
getCurrentStatusIndex = (id, featured) => { getCurrentStatusIndex = (id, featured) => {
if (featured) { if (featured) {
return this.props.featuredStatusIds.indexOf(id); return this.props.featuredStatusIds.keySeq().findIndex(key => key === id);
} else { } else {
return this.props.statusIds.indexOf(id) + this.getFeaturedStatusCount(); return this.props.statusIds.keySeq().findIndex(key => key === id) + this.getFeaturedStatusCount();
} }
} }

View File

@ -48,6 +48,7 @@ const messages = defineMessages({
reactionOpenMouth: { id: 'status.reactions.open_mouth', defaultMessage: 'Wow' }, reactionOpenMouth: { id: 'status.reactions.open_mouth', defaultMessage: 'Wow' },
reactionCry: { id: 'status.reactions.cry', defaultMessage: 'Sad' }, reactionCry: { id: 'status.reactions.cry', defaultMessage: 'Sad' },
reactionWeary: { id: 'status.reactions.weary', defaultMessage: 'Weary' }, reactionWeary: { id: 'status.reactions.weary', defaultMessage: 'Weary' },
emojiPickerExpand: { id: 'status.reactions_expand', defaultMessage: 'Select emoji' },
}); });
const mapStateToProps = state => { const mapStateToProps = state => {
@ -99,6 +100,9 @@ class ActionBar extends React.PureComponent {
isStaff: PropTypes.bool.isRequired, isStaff: PropTypes.bool.isRequired,
isAdmin: PropTypes.bool.isRequired, isAdmin: PropTypes.bool.isRequired,
allowedEmoji: ImmutablePropTypes.list, allowedEmoji: ImmutablePropTypes.list,
emojiSelectorFocused: PropTypes.bool,
handleEmojiSelectorExpand: PropTypes.func.isRequired,
handleEmojiSelectorUnfocus: PropTypes.func.isRequired,
}; };
static defaultProps = { static defaultProps = {
@ -107,6 +111,7 @@ class ActionBar extends React.PureComponent {
state = { state = {
emojiSelectorVisible: false, emojiSelectorVisible: false,
emojiSelectorFocused: false,
} }
handleReplyClick = () => { handleReplyClick = () => {
@ -169,10 +174,16 @@ class ActionBar extends React.PureComponent {
} else { } else {
this.props.onOpenUnauthorizedModal(); this.props.onOpenUnauthorizedModal();
} }
this.setState({ emojiSelectorVisible: false }); this.setState({ emojiSelectorVisible: false, emojiSelectorFocused: false });
}; };
} }
handleHotkeyEmoji = () => {
const { emojiSelectorVisible } = this.state;
this.setState({ emojiSelectorVisible: !emojiSelectorVisible });
}
handleDeleteClick = () => { handleDeleteClick = () => {
this.props.onDelete(this.props.status, this.context.router.history); this.props.onDelete(this.props.status, this.context.router.history);
} }
@ -262,12 +273,12 @@ class ActionBar extends React.PureComponent {
componentDidMount() { componentDidMount() {
document.addEventListener('click', e => { document.addEventListener('click', e => {
if (this.node && !this.node.contains(e.target)) if (this.node && !this.node.contains(e.target))
this.setState({ emojiSelectorVisible: false }); this.setState({ emojiSelectorVisible: false, emojiSelectorFocused: false });
}); });
} }
render() { render() {
const { status, intl, me, isStaff, isAdmin, allowedEmoji } = this.props; const { status, intl, me, isStaff, isAdmin, allowedEmoji, emojiSelectorFocused, handleEmojiSelectorExpand, handleEmojiSelectorUnfocus } = this.props;
const { emojiSelectorVisible } = this.state; const { emojiSelectorVisible } = this.state;
const ownAccount = status.getIn(['account', 'id']) === me; const ownAccount = status.getIn(['account', 'id']) === me;
@ -351,6 +362,7 @@ class ActionBar extends React.PureComponent {
let reblog_disabled = (status.get('visibility') === 'direct' || status.get('visibility') === 'private'); let reblog_disabled = (status.get('visibility') === 'direct' || status.get('visibility') === 'private');
return ( return (
<div className='detailed-status__action-bar'> <div className='detailed-status__action-bar'>
<div className='detailed-status__button'> <div className='detailed-status__button'>
@ -377,7 +389,12 @@ class ActionBar extends React.PureComponent {
onMouseLeave={this.handleLikeButtonLeave} onMouseLeave={this.handleLikeButtonLeave}
ref={this.setRef} ref={this.setRef}
> >
<EmojiSelector onReact={this.handleReactClick} visible={emojiSelectorVisible} /> <EmojiSelector
onReact={this.handleReactClick}
visible={emojiSelectorVisible}
focused={emojiSelectorFocused}
onUnfocus={handleEmojiSelectorUnfocus}
/>
<IconButton <IconButton
className='star-icon' className='star-icon'
animate animate
@ -388,6 +405,14 @@ class ActionBar extends React.PureComponent {
text={meEmojiTitle} text={meEmojiTitle}
onClick={this.handleLikeButtonClick} onClick={this.handleLikeButtonClick}
/> />
<IconButton
className='emoji-picker-expand'
animate
title={intl.formatMessage(messages.emojiPickerExpand)}
icon='caret-down'
onKeyUp={handleEmojiSelectorExpand}
onHover
/>
</div> </div>
{shareButton} {shareButton}
@ -400,4 +425,5 @@ class ActionBar extends React.PureComponent {
} }
export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(ActionBar)); export default injectIntl(
connect(mapStateToProps, mapDispatchToProps)(ActionBar));

View File

@ -146,6 +146,7 @@ class Status extends ImmutablePureComponent {
fullscreen: false, fullscreen: false,
showMedia: defaultMediaVisibility(this.props.status, this.props.displayMedia), showMedia: defaultMediaVisibility(this.props.status, this.props.displayMedia),
loadedStatusId: undefined, loadedStatusId: undefined,
emojiSelectorFocused: false,
}; };
componentDidMount() { componentDidMount() {
@ -363,6 +364,10 @@ class Status extends ImmutablePureComponent {
this.handleToggleMediaVisibility(); this.handleToggleMediaVisibility();
} }
handleHotkeyReact = () => {
this._expandEmojiSelector();
}
handleMoveUp = id => { handleMoveUp = id => {
const { status, ancestorsIds, descendantsIds } = this.props; const { status, ancestorsIds, descendantsIds } = this.props;
@ -397,6 +402,23 @@ class Status extends ImmutablePureComponent {
} }
} }
handleEmojiSelectorExpand = e => {
if (e.key === 'Enter') {
this._expandEmojiSelector();
}
e.preventDefault();
}
handleEmojiSelectorUnfocus = () => {
this.setState({ emojiSelectorFocused: false });
}
_expandEmojiSelector = () => {
this.setState({ emojiSelectorFocused: true });
const firstEmoji = this.status.querySelector('.emoji-react-selector .emoji-react-selector__emoji');
firstEmoji.focus();
};
_selectChild(index, align_top) { _selectChild(index, align_top) {
const container = this.node; const container = this.node;
const element = container.querySelectorAll('.focusable')[index]; const element = container.querySelectorAll('.focusable')[index];
@ -445,6 +467,10 @@ class Status extends ImmutablePureComponent {
this.node = c; this.node = c;
} }
setStatusRef = c => {
this.status = c;
}
componentDidUpdate(prevProps, prevState) { componentDidUpdate(prevProps, prevState) {
const { params, status } = this.props; const { params, status } = this.props;
const { ancestorsIds } = prevProps; const { ancestorsIds } = prevProps;
@ -510,6 +536,7 @@ class Status extends ImmutablePureComponent {
openProfile: this.handleHotkeyOpenProfile, openProfile: this.handleHotkeyOpenProfile,
toggleHidden: this.handleHotkeyToggleHidden, toggleHidden: this.handleHotkeyToggleHidden,
toggleSensitive: this.handleHotkeyToggleSensitive, toggleSensitive: this.handleHotkeyToggleSensitive,
react: this.handleHotkeyReact,
}; };
return ( return (
@ -537,7 +564,7 @@ class Status extends ImmutablePureComponent {
{ancestors} {ancestors}
<HotKeys handlers={handlers}> <HotKeys handlers={handlers}>
<div className={classNames('focusable', 'detailed-status__wrapper')} tabIndex='0' aria-label={textForScreenReader(intl, status, false)}> <div ref={this.setStatusRef} className={classNames('focusable', 'detailed-status__wrapper')} tabIndex='0' aria-label={textForScreenReader(intl, status, false)}>
<DetailedStatus <DetailedStatus
status={status} status={status}
onOpenVideo={this.handleOpenVideo} onOpenVideo={this.handleOpenVideo}
@ -569,6 +596,9 @@ class Status extends ImmutablePureComponent {
onToggleStatusSensitivity={this.handleToggleStatusSensitivity} onToggleStatusSensitivity={this.handleToggleStatusSensitivity}
onDeleteStatus={this.handleDeleteStatus} onDeleteStatus={this.handleDeleteStatus}
allowedEmoji={this.props.allowedEmoji} allowedEmoji={this.props.allowedEmoji}
emojiSelectorFocused={this.state.emojiSelectorFocused}
handleEmojiSelectorExpand={this.handleEmojiSelectorExpand}
handleEmojiSelectorUnfocus={this.handleEmojiSelectorUnfocus}
/> />
</div> </div>
</HotKeys> </HotKeys>

View File

@ -49,6 +49,10 @@ class HotkeysModal extends ImmutablePureComponent {
<td><kbd>f</kbd></td> <td><kbd>f</kbd></td>
<td><FormattedMessage id='keyboard_shortcuts.favourite' defaultMessage='to like' /></td> <td><FormattedMessage id='keyboard_shortcuts.favourite' defaultMessage='to like' /></td>
</tr> </tr>
<tr>
<td><kbd>e</kbd></td>
<td><FormattedMessage id='keyboard_shortcuts.react' defaultMessage='to react' /></td>
</tr>
<tr> <tr>
<td><kbd>b</kbd></td> <td><kbd>b</kbd></td>
<td><FormattedMessage id='keyboard_shortcuts.boost' defaultMessage='to repost' /></td> <td><FormattedMessage id='keyboard_shortcuts.boost' defaultMessage='to repost' /></td>

View File

@ -130,6 +130,7 @@ const keyMap = {
forceNew: 'option+n', forceNew: 'option+n',
reply: 'r', reply: 'r',
favourite: 'f', favourite: 'f',
react: 'e',
boost: 'b', boost: 'b',
mention: 'm', mention: 'm',
open: ['enter', 'o'], open: ['enter', 'o'],

View File

@ -403,6 +403,7 @@
"keyboard_shortcuts.notifications": "aby otworzyć kolumnę powiadomień", "keyboard_shortcuts.notifications": "aby otworzyć kolumnę powiadomień",
"keyboard_shortcuts.pinned": "aby przejść do listy przypiętych wpisów", "keyboard_shortcuts.pinned": "aby przejść do listy przypiętych wpisów",
"keyboard_shortcuts.profile": "aby przejść do profilu autora wpisu", "keyboard_shortcuts.profile": "aby przejść do profilu autora wpisu",
"keyboard_shortcuts.react": "aby zareagować na wpis",
"keyboard_shortcuts.reply": "aby odpowiedzieć", "keyboard_shortcuts.reply": "aby odpowiedzieć",
"keyboard_shortcuts.requests": "aby przejść do listy próśb o możliwość śledzenia", "keyboard_shortcuts.requests": "aby przejść do listy próśb o możliwość śledzenia",
"keyboard_shortcuts.search": "aby przejść do pola wyszukiwania", "keyboard_shortcuts.search": "aby przejść do pola wyszukiwania",
@ -707,6 +708,7 @@
"status.reactions.like": "Lubię", "status.reactions.like": "Lubię",
"status.reactions.open_mouth": "Wow", "status.reactions.open_mouth": "Wow",
"status.reactions.weary": "Nuda…", "status.reactions.weary": "Nuda…",
"status.reactions_expand": "Wybierz emoji",
"status.read_more": "Czytaj dalej", "status.read_more": "Czytaj dalej",
"status.reblog": "Podbij", "status.reblog": "Podbij",
"status.reblog_private": "Podbij dla odbiorców oryginalnego wpisu", "status.reblog_private": "Podbij dla odbiorców oryginalnego wpisu",

View File

@ -94,6 +94,22 @@
transform: translateY(-1px); transform: translateY(-1px);
} }
} }
.emoji-picker-expand {
display: none;
}
&:focus-within {
.emoji-picker-expand {
display: inline-flex;
width: 0;
overflow: hidden;
&:focus-within {
width: unset;
}
}
}
} }
.detailed-status__wrapper { .detailed-status__wrapper {

View File

@ -80,7 +80,8 @@
transition: 0.1s; transition: 0.1s;
z-index: 999; z-index: 999;
&--visible { &--visible,
&--focused {
opacity: 1; opacity: 1;
pointer-events: all; pointer-events: all;
} }
@ -99,7 +100,8 @@
transition: 0.1s; transition: 0.1s;
} }
&:hover { &:hover,
&:focus {
img { img {
width: 36px; width: 36px;
height: 36px; height: 36px;

View File

@ -677,3 +677,22 @@ a.status-card.compact:hover {
border-radius: 4px; border-radius: 4px;
} }
} }
.status__action-bar,
.detailed-status__action-bar {
.emoji-picker-expand {
display: none;
}
&:focus-within {
.emoji-picker-expand {
display: inline-flex;
width: 0;
overflow: hidden;
&:focus-within {
width: unset;
}
}
}
}