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

Wyświetl plik

@ -16,6 +16,7 @@ import classNames from 'classnames';
import Icon from 'soapbox/components/icon'; import Icon from 'soapbox/components/icon';
import PollContainer from 'soapbox/containers/poll_container'; import PollContainer from 'soapbox/containers/poll_container';
import { StatusInteractionBar } from './status_interaction_bar'; import { StatusInteractionBar } from './status_interaction_bar';
import UserPanel from '../../ui/components/user_panel';
export default class DetailedStatus extends ImmutablePureComponent { export default class DetailedStatus extends ImmutablePureComponent {
@ -38,6 +39,9 @@ export default class DetailedStatus extends ImmutablePureComponent {
state = { state = {
height: null, height: null,
profilePanelVisible: false,
profilePanelX: 0,
profilePanelY: 0,
}; };
handleOpenVideo = (media, startTime) => { 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'); 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() { render() {
const status = (this.props.status && this.props.status.get('reblog')) ? this.props.status.get('reblog') : this.props.status; const status = (this.props.status && this.props.status.get('reblog')) ? this.props.status.get('reblog') : this.props.status;
const outerStyle = { boxSizing: 'border-box' }; const outerStyle = { boxSizing: 'border-box' };
const { compact } = this.props; const { compact } = this.props;
const { profilePanelVisible, profilePanelX, profilePanelY } = this.state;
if (!status) { if (!status) {
return null; return null;
@ -158,10 +173,13 @@ export default class DetailedStatus extends ImmutablePureComponent {
return ( return (
<div style={outerStyle}> <div style={outerStyle}>
<div ref={this.setRef} className={classNames('detailed-status', { compact })}> <div ref={this.setRef} className={classNames('detailed-status', { compact })}>
<NavLink to={`/@${status.getIn(['account', 'acct'])}`} className='detailed-status__display-name'> <div className='detailed-status__profile' onMouseEnter={this.handleProfileHover} onMouseLeave={this.handleProfileLeave}>
<div className='detailed-status__display-avatar'><Avatar account={status.get('account')} size={48} /></div> <NavLink to={`/@${status.getIn(['account', 'acct'])}`} className='detailed-status__display-name'>
<DisplayName account={status.get('account')} /> <div className='detailed-status__display-avatar'><Avatar account={status.get('account')} size={48} /></div>
</NavLink> <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') && ( {status.get('group') && (
<div className='status__meta'> <div className='status__meta'>

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

@ -152,7 +152,6 @@
.status__info .status__display-name { .status__info .status__display-name {
display: block; display: block;
max-width: 100%; max-width: 100%;
padding-right: 25px;
} }
.status__info { .status__info {
@ -160,6 +159,27 @@
z-index: 4; 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 { .status-check-box {
border-bottom: 1px solid var(--background-color); border-bottom: 1px solid var(--background-color);
display: flex; display: flex;

Wyświetl plik

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