Show profile preview on hover

timeline-tab-hover-styles
Bárbara de Castro Fernandes 2020-06-16 09:06:44 -03:00 zatwierdzone przez Mary Kate
rodzic 30a5a0baa9
commit 563e4e5bab
7 zmienionych plików z 95 dodań i 25 usunięć

Wyświetl plik

@ -18,6 +18,7 @@ import classNames from 'classnames';
import Icon from 'soapbox/components/icon';
import PollContainer from 'soapbox/containers/poll_container';
import { NavLink } from 'react-router-dom';
import UserPanel from '../features/ui/components/user_panel';
// We use the component (and not the container) since we do not want
// to use the progress bar to show download progress
@ -104,6 +105,9 @@ class Status extends ImmutablePureComponent {
state = {
showMedia: defaultMediaVisibility(this.props.status, this.props.displayMedia),
statusId: undefined,
profilePanelVisible: false,
profilePanelX: 0,
profilePanelY: 0,
};
// Track height changes we know about to compensate scrolling
@ -249,6 +253,16 @@ class Status extends ImmutablePureComponent {
this.handleToggleMediaVisibility();
}
isMobile = () => window.matchMedia('only screen and (max-width: 895px)').matches;
handleProfileHover = e => {
if (!this.isMobile()) this.setState({ profilePanelVisible: true, profilePanelX: e.nativeEvent.offsetX, profilePanelY: e.nativeEvent.offsetY });
}
handleProfileLeave = e => {
if (!this.isMobile()) this.setState({ profilePanelVisible: false });
}
_properStatus() {
const { status } = this.props;
@ -435,6 +449,7 @@ class Status extends ImmutablePureComponent {
};
const statusUrl = `/@${status.getIn(['account', 'acct'])}/posts/${status.get('id')}`;
const { profilePanelVisible, profilePanelX, profilePanelY } = this.state;
return (
<HotKeys handlers={handlers}>
@ -448,13 +463,15 @@ class Status extends ImmutablePureComponent {
<RelativeTimestamp timestamp={status.get('created_at')} />
</NavLink>
<NavLink to={`/@${status.getIn(['account', 'acct'])}`} title={status.getIn(['account', 'acct'])} className='status__display-name'>
<div className='status__avatar'>
{statusAvatar}
</div>
<DisplayName account={status.get('account')} others={otherAccounts} />
</NavLink>
<div className='status__profile' onMouseEnter={this.handleProfileHover} onMouseLeave={this.handleProfileLeave}>
<NavLink to={`/@${status.getIn(['account', 'acct'])}`} title={status.getIn(['account', 'acct'])} className='status__display-name'>
<div className='status__avatar'>
{statusAvatar}
</div>
<DisplayName account={status.get('account')} others={otherAccounts} />
</NavLink>
<UserPanel accountId={status.getIn(['account', 'id'])} visible={profilePanelVisible} style={{ top: `${profilePanelY+15}px`, left: `${profilePanelX-132}px` }} />
</div>
</div>
{!group && status.get('group') && (

Wyświetl plik

@ -16,6 +16,7 @@ import classNames from 'classnames';
import Icon from 'soapbox/components/icon';
import PollContainer from 'soapbox/containers/poll_container';
import { StatusInteractionBar } from './status_interaction_bar';
import UserPanel from '../../ui/components/user_panel';
export default class DetailedStatus extends ImmutablePureComponent {
@ -38,6 +39,9 @@ export default class DetailedStatus extends ImmutablePureComponent {
state = {
height: null,
profilePanelVisible: false,
profilePanelX: 0,
profilePanelY: 0,
};
handleOpenVideo = (media, startTime) => {
@ -81,10 +85,21 @@ export default class DetailedStatus extends ImmutablePureComponent {
window.open(href, 'soapbox-intent', 'width=445,height=600,resizable=no,menubar=no,status=no,scrollbars=yes');
}
isMobile = () => window.matchMedia('only screen and (max-width: 895px)').matches;
handleProfileHover = e => {
if (!this.isMobile()) this.setState({ profilePanelVisible: true, profilePanelX: e.nativeEvent.offsetX, profilePanelY: e.nativeEvent.offsetY });
}
handleProfileLeave = e => {
if (!this.isMobile()) this.setState({ profilePanelVisible: false });
}
render() {
const status = (this.props.status && this.props.status.get('reblog')) ? this.props.status.get('reblog') : this.props.status;
const outerStyle = { boxSizing: 'border-box' };
const { compact } = this.props;
const { profilePanelVisible, profilePanelX, profilePanelY } = this.state;
if (!status) {
return null;
@ -158,10 +173,13 @@ export default class DetailedStatus extends ImmutablePureComponent {
return (
<div style={outerStyle}>
<div ref={this.setRef} className={classNames('detailed-status', { compact })}>
<NavLink to={`/@${status.getIn(['account', 'acct'])}`} className='detailed-status__display-name'>
<div className='detailed-status__display-avatar'><Avatar account={status.get('account')} size={48} /></div>
<DisplayName account={status.get('account')} />
</NavLink>
<div className='detailed-status__profile' onMouseEnter={this.handleProfileHover} onMouseLeave={this.handleProfileLeave}>
<NavLink to={`/@${status.getIn(['account', 'acct'])}`} className='detailed-status__display-name'>
<div className='detailed-status__display-avatar'><Avatar account={status.get('account')} size={48} /></div>
<DisplayName account={status.get('account')} />
<UserPanel accountId={status.getIn(['account', 'id'])} visible={profilePanelVisible} style={{ top: `${profilePanelY+15}px`, left: `${profilePanelX-132}px` }} />
</NavLink>
</div>
{status.get('group') && (
<div className='status__meta'>

Wyświetl plik

@ -10,6 +10,7 @@ import Avatar from 'soapbox/components/avatar';
import { shortNumberFormat } from 'soapbox/utils/numbers';
import { acctFull } from 'soapbox/utils/accounts';
import StillImage from 'soapbox/components/still_image';
import classNames from 'classnames';
class UserPanel extends ImmutablePureComponent {
@ -17,16 +18,23 @@ class UserPanel extends ImmutablePureComponent {
account: ImmutablePropTypes.map,
intl: PropTypes.object.isRequired,
domain: PropTypes.string,
style: PropTypes.object,
visible: PropTypes.bool,
}
static defaultProps = {
style: {},
visible: true,
}
render() {
const { account, intl, domain } = this.props;
const { account, intl, domain, style, visible } = this.props;
if (!account) return null;
const displayNameHtml = { __html: account.get('display_name_html') };
const acct = account.get('acct').indexOf('@') === -1 && domain ? `${account.get('acct')}@${domain}` : account.get('acct');
return (
<div className='user-panel'>
<div className={classNames('user-panel', { 'user-panel--visible': visible })} style={style}>
<div className='user-panel__container'>
<div className='user-panel__header'>
@ -84,17 +92,17 @@ class UserPanel extends ImmutablePureComponent {
};
const mapStateToProps = state => {
const me = state.get('me');
const makeMapStateToProps = () => {
const getAccount = makeGetAccount();
return {
account: getAccount(state, me),
};
const mapStateToProps = (state, { accountId }) => ({
account: getAccount(state, accountId),
});
return mapStateToProps;
};
export default injectIntl(
connect(mapStateToProps, null, null, {
connect(makeMapStateToProps, null, null, {
forwardRef: true,
})(UserPanel));

Wyświetl plik

@ -15,6 +15,7 @@ import { getFeatures } from 'soapbox/utils/features';
const mapStateToProps = state => {
const me = state.get('me');
return {
me,
account: state.getIn(['accounts', me]),
hasPatron: state.getIn(['soapbox', 'extensions', 'patron', 'enabled']),
features: getFeatures(state.get('instance')),
@ -30,7 +31,7 @@ class HomePage extends ImmutablePureComponent {
}
render() {
const { children, account, hasPatron, features } = this.props;
const { me, children, account, hasPatron, features } = this.props;
return (
<div className='page'>
@ -39,7 +40,7 @@ class HomePage extends ImmutablePureComponent {
<div className='columns-area__panels__pane columns-area__panels__pane--left'>
<div className='columns-area__panels__pane__inner'>
<UserPanel />
<UserPanel accountId={me} />
{hasPatron && <FundingPanel />}
<PromoPanel />
<LinkFooter />

Wyświetl plik

@ -20,7 +20,7 @@
.column,
.drawer {
flex: 1 1 100%;
overflow: hidden;
overflow: visible;
}
.drawer__pager {

Wyświetl plik

@ -152,7 +152,6 @@
.status__info .status__display-name {
display: block;
max-width: 100%;
padding-right: 25px;
}
.status__info {
@ -160,6 +159,27 @@
z-index: 4;
}
.status__profile,
.detailed-status__profile {
display: inline-block;
.user-panel {
position: absolute;
display: flex;
opacity: 0;
pointer-events: none;
transition-property: opacity;
transition-duration: 0.5s;
z-index: 999;
&--visible {
opacity: 1;
transition-delay: 1s;
pointer-events: all;
}
}
}
.status-check-box {
border-bottom: 1px solid var(--background-color);
display: flex;

Wyświetl plik

@ -3,7 +3,13 @@
display: flex;
width: 265px;
flex-direction: column;
overflow-y: hidden;
&,
.user-panel__account__name,
.user-panel__account__username {
overflow: hidden;
text-overflow: ellipsis;
}
&__header {
display: block;