diff --git a/app/soapbox/features/ui/components/action_button.js b/app/soapbox/features/ui/components/action_button.js
new file mode 100644
index 000000000..e1c88d232
--- /dev/null
+++ b/app/soapbox/features/ui/components/action_button.js
@@ -0,0 +1,98 @@
+import React from 'react';
+import { connect } from 'react-redux';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import PropTypes from 'prop-types';
+import { defineMessages, injectIntl } from 'react-intl';
+import Button from 'soapbox/components/button';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+import classNames from 'classnames';
+import {
+ followAccount,
+ unfollowAccount,
+ blockAccount,
+ unblockAccount,
+} from 'soapbox/actions/accounts';
+
+const messages = defineMessages({
+ unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
+ follow: { id: 'account.follow', defaultMessage: 'Follow' },
+ requested: { id: 'account.requested', defaultMessage: 'Awaiting approval. Click to cancel follow request' },
+ unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' },
+ edit_profile: { id: 'account.edit_profile', defaultMessage: 'Edit profile' },
+});
+
+const mapStateToProps = state => {
+ const me = state.get('me');
+ return {
+ me,
+ };
+};
+
+const mapDispatchToProps = (dispatch) => ({
+ onFollow(account) {
+ if (account.getIn(['relationship', 'following']) || account.getIn(['relationship', 'requested'])) {
+ dispatch(unfollowAccount(account.get('id')));
+ } else {
+ dispatch(followAccount(account.get('id')));
+ }
+ },
+
+ onBlock(account) {
+ if (account.getIn(['relationship', 'blocking'])) {
+ dispatch(unblockAccount(account.get('id')));
+ } else {
+ dispatch(blockAccount(account.get('id')));
+ }
+ },
+});
+
+export default @connect(mapStateToProps, mapDispatchToProps)
+@injectIntl
+class ActionButton extends ImmutablePureComponent {
+
+ static propTypes = {
+ account: ImmutablePropTypes.map.isRequired,
+ onFollow: PropTypes.func.isRequired,
+ onBlock: PropTypes.func.isRequired,
+ intl: PropTypes.object.isRequired,
+ };
+
+ componentDidMount() {
+ window.addEventListener('resize', this.handleResize, { passive: true });
+ }
+
+ componentWillUnmount() {
+ window.removeEventListener('resize', this.handleResize);
+ }
+
+ handleFollow = () => {
+ this.props.onFollow(this.props.account);
+ }
+
+ handleBlock = () => {
+ this.props.onBlock(this.props.account);
+ }
+
+ render() {
+ const { account, intl, me } = this.props;
+ let actionBtn = null;
+
+ if (!account || !me) return actionBtn;
+
+ if (me !== account.get('id')) {
+ if (!account.get('relationship')) { // Wait until the relationship is loaded
+ //
+ } else if (account.getIn(['relationship', 'requested'])) {
+ actionBtn =
;
+ } else if (!account.getIn(['relationship', 'blocking'])) {
+ actionBtn =
;
+ } else if (account.getIn(['relationship', 'blocking'])) {
+ actionBtn =
;
+ }
+ } else {
+ actionBtn =
;
+ }
+ return actionBtn;
+ }
+
+}
diff --git a/app/soapbox/features/ui/components/user_panel.js b/app/soapbox/features/ui/components/user_panel.js
index 83dfd2099..735830622 100644
--- a/app/soapbox/features/ui/components/user_panel.js
+++ b/app/soapbox/features/ui/components/user_panel.js
@@ -84,17 +84,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));
diff --git a/app/soapbox/pages/home_page.js b/app/soapbox/pages/home_page.js
index f06bd90eb..eeeafede9 100644
--- a/app/soapbox/pages/home_page.js
+++ b/app/soapbox/pages/home_page.js
@@ -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 (
@@ -39,7 +40,7 @@ class HomePage extends ImmutablePureComponent {
-
+
{hasPatron &&
}
diff --git a/app/styles/application.scss b/app/styles/application.scss
index bb3d0ca82..b040685c9 100644
--- a/app/styles/application.scss
+++ b/app/styles/application.scss
@@ -71,3 +71,4 @@
@import 'components/error-boundary';
@import 'components/video-player';
@import 'components/audio-player';
+@import 'components/profile_hover_card';
diff --git a/app/styles/basics.scss b/app/styles/basics.scss
index 2dafa35a3..53119ffc4 100644
--- a/app/styles/basics.scss
+++ b/app/styles/basics.scss
@@ -218,3 +218,14 @@ noscript {
}
}
}
+
+.floating-link {
+ width: 100%;
+ height: 100%;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ position: absolute;
+ z-index: 9999;
+}
diff --git a/app/styles/components/display-name.scss b/app/styles/components/display-name.scss
index 2282c6d48..0dd3e1e65 100644
--- a/app/styles/components/display-name.scss
+++ b/app/styles/components/display-name.scss
@@ -41,6 +41,7 @@ a.account__display-name {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
+ position: relative;
}
.display-name__html {
diff --git a/app/styles/components/drawer.scss b/app/styles/components/drawer.scss
index 7dc5a1a10..39e936158 100644
--- a/app/styles/components/drawer.scss
+++ b/app/styles/components/drawer.scss
@@ -20,7 +20,7 @@
.column,
.drawer {
flex: 1 1 100%;
- overflow: hidden;
+ overflow: visible;
}
.drawer__pager {
diff --git a/app/styles/components/profile_hover_card.scss b/app/styles/components/profile_hover_card.scss
new file mode 100644
index 000000000..0910061cb
--- /dev/null
+++ b/app/styles/components/profile_hover_card.scss
@@ -0,0 +1,96 @@
+.display-name__account {
+ position: relative;
+ cursor: pointer;
+}
+
+.display-name .profile-hover-card {
+ white-space: normal;
+}
+
+.profile-hover-card {
+ position: absolute;
+ pointer-events: none;
+ opacity: 0;
+ transition-property: opacity;
+ transition-duration: 0.2s;
+ transition-delay: 0.7s;
+ width: 300px;
+ z-index: 998;
+ left: -10px;
+ padding-top: 20px;
+ margin-bottom: 10px;
+
+ &--visible {
+ opacity: 1;
+ pointer-events: all;
+ }
+
+ @media(min-width: 750px) {
+ left: -80px;
+ }
+
+ .profile-hover-card__container {
+ @include standard-panel;
+ position: relative;
+ overflow: hidden;
+ }
+
+ .profile-hover-card__action-button {
+ z-index: 999;
+ position: absolute;
+ right: 20px;
+ top: 120px;
+ }
+
+ .user-panel {
+ box-shadow: none;
+ width: auto;
+
+ .user-panel-stats-item a strong {
+ text-decoration: none;
+ }
+ }
+
+ .relationship-tag {
+ position: absolute;
+ top: 10px;
+ left: 10px;
+ z-index: 1;
+ }
+
+ .profile-hover-card__badges {
+ margin: 0 20px 20px;
+ display: flex;
+
+ .badge {
+ padding: 2px 4px;
+ margin-right: 5px;
+ border-radius: 3px;
+ font-size: 11px;
+ opacity: 1;
+ }
+ }
+
+ .profile-hover-card__bio {
+ margin: 0 20px 20px;
+ max-height: 4em;
+
+ &::after {
+ content: '';
+ display: block;
+ position: absolute;
+ width: 100%;
+ height: 20px;
+ bottom: 0;
+ left: 0;
+ background: linear-gradient(0deg, var(--foreground-color) 0%, var(--foreground-color), 80%, transparent);
+ }
+ }
+}
+
+.detailed-status {
+ .profile-hover-card {
+ top: 0;
+ left: 80px;
+ }
+}
diff --git a/app/styles/components/status.scss b/app/styles/components/status.scss
index f79e1cfba..c0ada2670 100644
--- a/app/styles/components/status.scss
+++ b/app/styles/components/status.scss
@@ -210,7 +210,6 @@
.status__info .status__display-name {
display: block;
max-width: 100%;
- padding-right: 25px;
}
.status__info {
@@ -218,6 +217,11 @@
z-index: 4;
}
+.status__profile,
+.detailed-status__profile {
+ display: inline-block;
+}
+
.status-check-box {
border-bottom: 1px solid var(--background-color);
display: flex;
diff --git a/app/styles/components/user-panel.scss b/app/styles/components/user-panel.scss
index cf49a3d68..44ec3cd9b 100644
--- a/app/styles/components/user-panel.scss
+++ b/app/styles/components/user-panel.scss
@@ -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;