Skip to content

Commit 663aa39

Browse files
Sidebar collapse (#1760)
* feat: Sidebar collapse * fix: Handling app name text-overflow * fix overlaying JS console line number bar * removed toolbar ease-in animation * fixed pin icon padding * Update CHANGELOG.md * fixed pinned collapse state * fixed uncollapsing on mobile Co-authored-by: Douglas Muraoka <[email protected]>
1 parent 1ddf29e commit 663aa39

19 files changed

+264
-120
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
__New features:__
77
* Added data export in CSV format for classes ([#1494](https://github.com/parse-community/parse-dashboard/pull/1494)), thanks to [Cory Imdieke](https://github.com/Vortec4800), [Manuel Trezza](https://github.com/mtrezza).
8+
* Collapse sidebar ([#1760](https://github.com/parse-community/parse-dashboard/pull/1760)), thanks to [Douglas Muraoka](https://github.com/douglasmuraoka), [Manuel Trezza](https://github.com/mtrezza).
89

910
### 2.1.0
1011
[Full Changelog](https://github.com/parse-community/parse-dashboard/compare/2.0.5...2.1.0)
Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,18 @@
11
import React from 'react';
2+
import Pin from 'components/Sidebar/Pin.react';
23
import styles from 'components/Sidebar/Sidebar.scss';
34

4-
export default ({ name, onClick }) => (
5-
<div className={styles.currentApp} onClick={onClick}>
6-
{name}
5+
const AppName = ({ name, onClick, onPinClick }) => (
6+
<div>
7+
<div className={styles.currentApp}>
8+
<div className={styles.appNameContainer} onClick={onClick}>
9+
<div className={styles.currentAppName}>
10+
{name}
11+
</div>
12+
</div>
13+
<Pin onClick={onPinClick} />
14+
</div>
715
</div>
816
);
17+
18+
export default AppName;

src/components/Sidebar/AppsMenu.react.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ import React from 'react';
1313
import styles from 'components/Sidebar/Sidebar.scss';
1414
import { unselectable } from 'stylesheets/base.scss';
1515

16-
let AppsMenu = ({ apps, current, height, onSelect }) => (
16+
const AppsMenu = ({ apps, current, height, onSelect, onPinClick }) => (
1717
<div style={{ height }} className={[styles.appsMenu, unselectable].join(' ')}>
18-
<AppName name={current.name} onClick={onSelect.bind(null, current.slug)} />
18+
<AppName name={current.name} onClick={onSelect.bind(null, current.slug)} onPinClick={onPinClick} />
1919
<div className={styles.menuSection}>All Apps</div>
2020
<div className={styles.appListContainer}>
2121
{apps.map((app) => {

src/components/Sidebar/FooterMenu.react.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,14 @@ export default class FooterMenu extends React.Component {
3333
}
3434

3535
render() {
36+
if (this.props.isCollapsed) {
37+
return (
38+
<div className={styles.more}>
39+
<Icon height={24} width={24} name='ellipses' />
40+
</div>
41+
);
42+
}
43+
3644
let content = null;
3745
if (this.state.show) {
3846
content = (

src/components/Sidebar/Pin.react.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import React from "react";
2+
3+
import Icon from "components/Icon/Icon.react";
4+
import styles from "components/Sidebar/Sidebar.scss";
5+
6+
const Pin = ({ onClick }) => (
7+
<div className={styles.pinContainer} onClick={onClick}>
8+
<Icon className={styles.sidebarPin} name="pin" width={20} height={20} />
9+
</div>
10+
);
11+
12+
export default Pin;

src/components/Sidebar/Sidebar.react.js

Lines changed: 97 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,10 @@ import AppsManager from 'lib/AppsManager';
1010
import AppsMenu from 'components/Sidebar/AppsMenu.react';
1111
import AppName from 'components/Sidebar/AppName.react';
1212
import FooterMenu from 'components/Sidebar/FooterMenu.react';
13-
import React, { useState } from 'react';
13+
import isInsidePopover from 'lib/isInsidePopover';
1414
import ParseApp from 'lib/ParseApp';
15+
import Pin from 'components/Sidebar/Pin.react';
16+
import React, { useEffect, useState } from 'react';
1517
import SidebarHeader from 'components/Sidebar/SidebarHeader.react';
1618
import SidebarSection from 'components/Sidebar/SidebarSection.react';
1719
import SidebarSubItem from 'components/Sidebar/SidebarSubItem.react';
@@ -29,7 +31,46 @@ const Sidebar = ({
2931
primaryBackgroundColor,
3032
secondaryBackgroundColor
3133
}, { currentApp }) => {
34+
const collapseWidth = 980;
3235
const [ appsMenuOpen, setAppsMenuOpen ] = useState(false);
36+
const [ collapsed, setCollapsed ] = useState(false);
37+
const [ fixed, setFixed ] = useState(true);
38+
let currentWidth = window.innerWidth;
39+
40+
const windowResizeHandler = () => {
41+
if (window.innerWidth <= collapseWidth && currentWidth > collapseWidth) {
42+
if (document.body.className.indexOf(' expanded') === -1) {
43+
document.body.className += ' expanded';
44+
}
45+
setCollapsed(true);
46+
setFixed(false);
47+
} else if (window.innerWidth > collapseWidth && currentWidth <= collapseWidth) {
48+
document.body.className = document.body.className.replace(' expanded', '');
49+
setCollapsed(false);
50+
setFixed(true);
51+
}
52+
// Update window width
53+
currentWidth = window.innerWidth;
54+
}
55+
56+
useEffect(() => {
57+
window.addEventListener('resize', windowResizeHandler);
58+
59+
return () => {
60+
window.removeEventListener('resize', windowResizeHandler);
61+
}
62+
});
63+
64+
const sidebarClasses = [styles.sidebar];
65+
if (fixed) {
66+
document.body.className = document.body.className.replace(' expanded', '');
67+
} else if (!fixed && collapsed) {
68+
sidebarClasses.push(styles.collapsed);
69+
if (document.body.className.indexOf(' expanded') === -1) {
70+
document.body.className += ' expanded';
71+
}
72+
}
73+
3374
const _subMenu = subsections => {
3475
if (!subsections) {
3576
return null;
@@ -54,25 +95,40 @@ const Sidebar = ({
5495
);
5596
}
5697

57-
const apps = [].concat(AppsManager.apps()).sort((a, b) => (a.name < b.name ? -1 : (a.name > b.name ? 1 : 0)));
98+
const onPinClick = () => {
99+
if (fixed) {
100+
setFixed(false);
101+
setCollapsed(true);
102+
setAppsMenuOpen(false);
103+
} else {
104+
setFixed(true);
105+
setCollapsed(false);
106+
}
107+
};
58108

59109
let sidebarContent;
60110
if (appsMenuOpen) {
111+
const apps = [].concat(AppsManager.apps()).sort((a, b) => (a.name < b.name ? -1 : (a.name > b.name ? 1 : 0)));
61112
sidebarContent = (
62113
<AppsMenu
63114
apps={apps}
64115
current={currentApp}
116+
onPinClick={onPinClick}
65117
onSelect={() => setAppsMenuOpen(false)} />
66118
);
67119
} else {
120+
const topContent = collapsed
121+
? <Pin onClick={onPinClick} />
122+
: appSelector && (
123+
<div className={styles.apps}>
124+
<AppName name={currentApp.name} onClick={() => setAppsMenuOpen(true)} onPinClick={onPinClick} />
125+
</div>
126+
) || undefined;
127+
68128
sidebarContent = (
69129
<>
70-
{appSelector && (
71-
<div className={styles.apps}>
72-
<AppName name={currentApp.name} onClick={() => setAppsMenuOpen(true)} />
73-
</div>
74-
)}
75130
<div className={styles.content}>
131+
{topContent}
76132
{sections.map(({
77133
name,
78134
icon,
@@ -84,15 +140,15 @@ const Sidebar = ({
84140
return (
85141
<SidebarSection
86142
key={name}
87-
name={name}
143+
name={collapsed ? null : name}
88144
icon={icon}
89145
style={style}
90146
link={prefix + link}
91147
active={active}
92148
primaryBackgroundColor={primaryBackgroundColor}
93149
secondaryBackgroundColor={secondaryBackgroundColor}
94150
>
95-
{active ? _subMenu(subsections) : null}
151+
{!collapsed && active ? _subMenu(subsections) : null}
96152
</SidebarSection>
97153
);
98154
})}
@@ -101,16 +157,39 @@ const Sidebar = ({
101157
)
102158
}
103159

104-
return <div className={styles.sidebar}>
105-
<SidebarHeader />
106-
{sidebarContent}
107-
<div className={styles.footer}>
108-
<a target='_blank' href='http://parseplatform.org/'>Open Source Hub</a>
109-
<a target='_blank' href='https://github.com/parse-community'>GitHub</a>
110-
<a target='_blank' href='http://docs.parseplatform.org/'>Docs</a>
111-
<FooterMenu />
160+
return (
161+
<div
162+
className={sidebarClasses.join(' ')}
163+
onMouseEnter={
164+
!fixed && collapsed
165+
? () => setCollapsed(false)
166+
: undefined
167+
}
168+
onMouseLeave={
169+
!collapsed && !fixed
170+
? (e => {
171+
if (!isInsidePopover(e.relatedTarget)) {
172+
setAppsMenuOpen(false);
173+
setCollapsed(true);
174+
}
175+
})
176+
: undefined
177+
}
178+
>
179+
<SidebarHeader isCollapsed={!appsMenuOpen && collapsed} />
180+
{sidebarContent}
181+
<div className={styles.footer}>
182+
{!collapsed && (
183+
<>
184+
<a target='_blank' href='http://parseplatform.org/'>Open Source Hub</a>
185+
<a target='_blank' href='https://github.com/parse-community'>GitHub</a>
186+
<a target='_blank' href='http://docs.parseplatform.org/'>Docs</a>
187+
</>
188+
)}
189+
<FooterMenu isCollapsed={!appsMenuOpen && collapsed} />
190+
</div>
112191
</div>
113-
</div>
192+
);
114193
}
115194

116195
Sidebar.contextTypes = {

0 commit comments

Comments
 (0)