Show profile preview on hover
This commit is contained in:
parent
30a5a0baa9
commit
563e4e5bab
7 changed files with 95 additions and 25 deletions
|
@ -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') && (
|
||||
|
|
|
@ -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'>
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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 />
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
.column,
|
||||
.drawer {
|
||||
flex: 1 1 100%;
|
||||
overflow: hidden;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.drawer__pager {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue