diff --git a/app/javascript/gabsocial/actions/sidebar.js b/app/javascript/gabsocial/actions/sidebar.js
new file mode 100644
index 00000000..bd646796
--- /dev/null
+++ b/app/javascript/gabsocial/actions/sidebar.js
@@ -0,0 +1,14 @@
+export const SIDEBAR_OPEN = 'SIDEBAR_OPEN';
+export const SIDEBAR_CLOSE = 'SIDEBAR_CLOSE';
+
+export function openSidebar() {
+ return {
+ type: SIDEBAR_OPEN,
+ };
+};
+
+export function closeSidebar() {
+ return {
+ type: SIDEBAR_CLOSE,
+ };
+};
diff --git a/app/javascript/gabsocial/components/sidebar_menu.js b/app/javascript/gabsocial/components/sidebar_menu.js
new file mode 100644
index 00000000..9fc45262
--- /dev/null
+++ b/app/javascript/gabsocial/components/sidebar_menu.js
@@ -0,0 +1,161 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { connect } from 'react-redux';
+import { Link, NavLink } from 'react-router-dom';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+import { injectIntl, defineMessages } from 'react-intl';
+import classNames from 'classnames';
+import Avatar from './avatar';
+import IconButton from './icon_button';
+import Icon from './icon';
+import DisplayName from './display_name';
+import { closeSidebar } from '../actions/sidebar';
+import { shortNumberFormat } from '../utils/numbers';
+import { me } from '../initial_state';
+import { makeGetAccount } from '../selectors';
+
+const messages = defineMessages({
+ followers: { id: 'account.followers', defaultMessage: 'Followers' },
+ follows: { id: 'account.follows', defaultMessage: 'Follows' },
+ profile: { id: 'account.profile', defaultMessage: 'Profile' },
+ preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' },
+ follow_requests: { id: 'navigation_bar.follow_requests', defaultMessage: 'Follow requests' },
+ blocks: { id: 'navigation_bar.blocks', defaultMessage: 'Blocked users' },
+ domain_blocks: { id: 'navigation_bar.domain_blocks', defaultMessage: 'Hidden domains' },
+ mutes: { id: 'navigation_bar.mutes', defaultMessage: 'Muted users' },
+ filters: { id: 'navigation_bar.filters', defaultMessage: 'Muted words' },
+ logout: { id: 'navigation_bar.logout', defaultMessage: 'Logout' },
+ lists: { id: 'column.lists', defaultMessage: 'Lists', },
+ apps: { id: 'tabs_bar.apps', defaultMessage: 'Apps' },
+ news: { id: 'tabs_bar.news', defaultMessage: 'News' },
+})
+
+const mapStateToProps = state => {
+ const getAccount = makeGetAccount();
+
+ return {
+ account: getAccount(state, me),
+ sidebarOpen: state.get('sidebar').sidebarOpen,
+ };
+};
+
+const mapDispatchToProps = (dispatch) => ({
+ onClose () {
+ dispatch(closeSidebar());
+ },
+});
+
+export default @connect(mapStateToProps, mapDispatchToProps)
+@injectIntl
+class SidebarMenu extends ImmutablePureComponent {
+
+ static propTypes = {
+ intl: PropTypes.object.isRequired,
+ account: ImmutablePropTypes.map,
+ sidebarOpen: PropTypes.bool,
+ onClose: PropTypes.func.isRequired,
+ };
+
+ render () {
+ const { sidebarOpen, onClose, intl, account } = this.props;
+ const acct = account.get('acct');
+
+ const classes = classNames('sidebar-menu__root', {
+ 'sidebar-menu__root--visible': sidebarOpen,
+ });
+
+ return (
+
+
+
+
+
+ Account Info
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {shortNumberFormat(account.get('followers_count'))}
+ {intl.formatMessage(messages.followers)}
+
+
+ {shortNumberFormat(account.get('following_count'))}
+ {intl.formatMessage(messages.follows)}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+
+}
diff --git a/app/javascript/gabsocial/features/ui/components/tabs_bar.js b/app/javascript/gabsocial/features/ui/components/tabs_bar.js
index ee4bce0b..f94f1442 100644
--- a/app/javascript/gabsocial/features/ui/components/tabs_bar.js
+++ b/app/javascript/gabsocial/features/ui/components/tabs_bar.js
@@ -11,6 +11,7 @@ import SearchContainer from 'gabsocial/features/compose/containers/search_contai
import Avatar from '../../../components/avatar';
import ActionBar from 'gabsocial/features/compose/components/action_bar';
import { openModal } from '../../../actions/modal';
+import { openSidebar } from '../../../actions/sidebar';
export const privateLinks = [
@@ -60,6 +61,7 @@ class TabsBar extends React.PureComponent {
intl: PropTypes.object.isRequired,
history: PropTypes.object.isRequired,
onOpenCompose: PropTypes.func,
+ onOpenSidebar: PropTypes.func.isRequired,
}
state = {
@@ -182,6 +184,7 @@ class TabsBar extends React.PureComponent {
{pathTitle}
@@ -218,6 +221,9 @@ const mapDispatchToProps = (dispatch) => ({
onOpenCompose() {
dispatch(openModal('COMPOSE'));
},
+ onOpenSidebar() {
+ dispatch(openSidebar());
+ },
});
export default injectIntl(
diff --git a/app/javascript/gabsocial/features/ui/index.js b/app/javascript/gabsocial/features/ui/index.js
index 7aa604b1..43c18ede 100644
--- a/app/javascript/gabsocial/features/ui/index.js
+++ b/app/javascript/gabsocial/features/ui/index.js
@@ -30,6 +30,7 @@ import GroupPage from 'gabsocial/pages/group_page';
import SearchPage from 'gabsocial/pages/search_page';
import HomePage from 'gabsocial/pages/home_page';
import GroupSidebarPanel from '../groups/sidebar_panel';
+import SidebarMenu from '../../components/sidebar_menu';
import {
Status,
@@ -539,6 +540,7 @@ class UI extends React.PureComponent {
+
);
diff --git a/app/javascript/gabsocial/reducers/index.js b/app/javascript/gabsocial/reducers/index.js
index 06370d27..82921f51 100644
--- a/app/javascript/gabsocial/reducers/index.js
+++ b/app/javascript/gabsocial/reducers/index.js
@@ -36,6 +36,7 @@ import groups from './groups';
import group_relationships from './group_relationships';
import group_lists from './group_lists';
import group_editor from './group_editor';
+import sidebar from './sidebar';
const reducers = {
dropdown_menu,
@@ -75,6 +76,7 @@ const reducers = {
group_relationships,
group_lists,
group_editor,
+ sidebar,
};
export default combineReducers(reducers);
diff --git a/app/javascript/gabsocial/reducers/sidebar.js b/app/javascript/gabsocial/reducers/sidebar.js
new file mode 100644
index 00000000..92d14839
--- /dev/null
+++ b/app/javascript/gabsocial/reducers/sidebar.js
@@ -0,0 +1,12 @@
+import { SIDEBAR_OPEN, SIDEBAR_CLOSE } from '../actions/sidebar';
+
+export default function sidebar(state={}, action) {
+ switch(action.type) {
+ case SIDEBAR_OPEN:
+ return { sidebarOpen: true };
+ case SIDEBAR_CLOSE:
+ return { sidebarOpen: false };
+ default:
+ return state;
+ }
+};
diff --git a/app/javascript/styles/application.scss b/app/javascript/styles/application.scss
index ab469abd..4cb2e478 100644
--- a/app/javascript/styles/application.scss
+++ b/app/javascript/styles/application.scss
@@ -31,6 +31,7 @@
@import 'gabsocial/components/group-accounts';
@import 'gabsocial/components/group-form';
@import 'gabsocial/components/group-sidebar-panel';
+@import 'gabsocial/components/sidebar-menu';
@import 'gabsocial/polls';
@import 'gabsocial/introduction';
diff --git a/app/javascript/styles/gabsocial/components/sidebar-menu.scss b/app/javascript/styles/gabsocial/components/sidebar-menu.scss
new file mode 100644
index 00000000..36d80b77
--- /dev/null
+++ b/app/javascript/styles/gabsocial/components/sidebar-menu.scss
@@ -0,0 +1,171 @@
+.sidebar-menu {
+ display: flex;
+ position: fixed;
+ flex-direction: column;
+ width: 275px;
+ height: 100vh;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ background-color: #fff;
+ transform: translateX(-275px);
+ transition: all 0.15s linear;
+ z-index: 10001;
+
+ &__root {
+ display: none;
+ }
+
+ &__wrapper {
+ display: block;
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ z-index: 10000;
+ background-color: transparent;
+ transition: background-color 0.2s linear;
+ transition-delay: 0.1s;
+ }
+
+ &__content {
+ display: flex;
+ flex: 1 1;
+ flex-direction: column;
+ padding-bottom: 40px;
+ overflow: hidden;
+ overflow-y: scroll;
+ -webkit-overflow-scrolling: touch;
+ }
+
+ &__section {
+ display: flex;
+ flex-direction: column;
+ margin: 4px 0;
+ border-top: 1px solid #ddd;
+
+ &--borderless {
+ margin: 0;
+ border-top: none;
+ }
+ }
+
+ @media (max-width: 400px) {
+ width: 90vw;
+ }
+}
+
+.sidebar-menu__root--visible {
+ display: block;
+
+ .sidebar-menu {
+ transform: translateX(0);
+ }
+
+ .sidebar-menu__wrapper {
+ background-color: rgba(0,0,0,0.3);
+ }
+}
+
+.sidebar-menu-header {
+ display: flex;
+ height: 46px;
+ padding: 12px 14px;
+ border-bottom: 1px solid #ddd;
+ box-sizing: border-box;
+ align-items: center;
+
+ &__title {
+ display: block;
+ font-size: 18px;
+ font-weight: 600;
+ color: #000;
+ }
+
+ &__btn {
+ margin-left: auto;
+ }
+}
+
+.sidebar-menu-profile {
+ display: flex;
+ flex-direction: column;
+ width: 100%;
+ padding: 14px 18px;
+
+ &__avatar {
+ display: block;
+ width: 56px;
+ height: 56px;
+ }
+
+ &__name {
+ display: block;
+ margin-top: 10px;
+
+ .display-name__account {
+ display: block;
+ margin-top: 2px;
+ }
+ }
+
+ &__stats {
+ display: flex;
+ margin-top: 12px;
+ }
+}
+
+.sidebar-menu-profile-stat {
+ display: flex;
+ font-size: 14px;
+ text-decoration: none;
+
+ &:not(:first-of-type) {
+ margin-left: 18px;
+ }
+
+ &__value {
+ display: flex;
+ margin-right: 3px;
+ font-weight: 700;
+ color: #000;
+ }
+
+ &__label {
+ display: flex;
+ color: #444;
+ }
+
+ &:hover {
+ text-decoration: underline;
+ }
+}
+
+.sidebar-menu-item {
+ display: flex;
+ padding: 16px 18px;
+ cursor: pointer;
+ text-decoration: none;
+ color: #666;
+ font-size: 15px;
+ font-weight: 400;
+
+ &:hover {
+ background-color: rgba($gab-brand-default, 0.1);
+
+ .fa {
+ color: #000;
+ }
+ }
+
+ .fa {
+ margin-right: 10px;
+ }
+
+ &:hover {
+ &__title {
+ color: #000;
+ }
+ }
+}
\ No newline at end of file