forked from webpack/webpack.js.org
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathSidebarMobile.jsx
More file actions
234 lines (203 loc) · 7.16 KB
/
SidebarMobile.jsx
File metadata and controls
234 lines (203 loc) · 7.16 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
import { clsx } from "clsx";
import PropTypes from "prop-types";
import { Component } from "react";
import CloseIcon from "../../styles/icons/cross.svg";
import Link from "../Link/Link.jsx";
// TODO: Check to make sure all pages are shown and properly sorted
export default class SidebarMobile extends Component {
_container = null;
_initialTouchPosition = {};
_lastTouchPosition = {};
static propTypes = {
isOpen: PropTypes.bool,
toggle: PropTypes.func,
sections: PropTypes.array,
};
componentDidMount() {
if (this.props.isOpen) {
this._toggleBodyListener(true);
}
}
componentDidUpdate(prevProps) {
if (prevProps.isOpen !== this.props.isOpen) {
this._toggleBodyListener(this.props.isOpen);
}
}
componentWillUnmount() {
this._toggleBodyListener(false);
}
render() {
const { isOpen, toggle } = this.props;
return (
<nav
className={clsx(
"sidebar-mobile fixed w-[300px] h-screen z-[100] top-0 overflow-y-auto overflow-x-hidden [-webkit-overflow-scrolling:touch] transition-transform duration-500 ease-[cubic-bezier(0.23,1,0.32,1)] md:hidden",
isOpen
? "sidebar-mobile--visible translate-x-0"
: "[transform:translate3d(calc(-100%+5px),0,0)]",
)}
ref={(ref) => (this._container = ref)}
onTouchStart={this._handleTouchStart}
onTouchMove={this._handleTouchMove}
onTouchEnd={this._handleTouchEnd}
>
<div
className={clsx(
"absolute top-[45px] bottom-0 w-[32px] left-[285px]",
isOpen && "hidden",
)}
onTouchStart={this._handleTouchStart}
onTouchMove={this._handleOpenerTouchMove}
onTouchEnd={this._handleTouchEnd}
/>
<div className="relative w-[285px] h-screen overflow-x-hidden py-1 bg-white dark:bg-[#121212] shadow-[0_0_15px_rgba(0,0,0,0.2)]">
<button
className="sidebar-mobile__close absolute cursor-pointer border-none right-[22px] top-[10px] text-[1.3em] bg-[#175d96] text-white w-[30px] h-[30px] flex items-center justify-center rounded-full transition-colors duration-150 [-webkit-tap-highlight-color:transparent] hover:bg-[#135d96]"
onClick={toggle.bind(null, false)}
aria-label="Close navigation menu"
>
<CloseIcon fill="#fff" width={20} />
</button>
{this._getSections()}
</div>
</nav>
);
}
_toggleBodyListener = (add) => {
const actionName = add ? "addEventListener" : "removeEventListener";
window[actionName]("touchstart", this._handleBodyClick);
window[actionName]("mousedown", this._handleBodyClick);
};
/**
* Get markup for each section
*
* @return {array} - Markup containing sections and links
*/
_getSections() {
let pathname = "";
if (window && window.location !== undefined) {
pathname = window.location.pathname;
}
return this.props.sections.map((section, index) => {
const active = section.url !== "/" && pathname.startsWith(section.url);
return (
<div
className={clsx(
"border-l-2 pb-[0.5em]",
active ? "border-blue-200" : "border-transparent",
)}
key={section.url}
>
<Link
className={clsx(
"uppercase pt-[0.75em] px-4 pb-[0.25em] font-semibold block text-[1.1rem]",
active ? "text-[#465E69]" : "text-[#2B3A42]",
index > 0 && "border-t border-gray-200 dark:border-[#343434]",
"dark:text-[#cadbe6]",
)}
key={section.url}
to={section.url}
onClick={this.props.toggle.bind(null, false)}
>
<h3>{section.title || section.url}</h3>
</Link>
{this._getPages(section.children)}
</div>
);
});
}
/**
* Retrieve markup for page links
*
* @param {array} pages - A list of page objects
* @return {array} - Markup containing the page links
*/
_getPages(pages) {
let pathname = "";
if (window.location !== undefined) {
pathname = window.location.pathname;
}
return pages.map((page) => {
const url = `${page.url}`;
const active = pathname === url;
return (
<Link
key={url}
className={clsx(
"block py-[0.5em] px-[17px] capitalize [-webkit-tap-highlight-color:transparent] ml-[20px]",
active
? "text-gray-900 font-semibold bg-[#f1f4f4] dark:text-white dark:bg-[#222424]"
: "text-gray-600 dark:text-[#a3a3a3] hover:text-gray-600 active:text-gray-900 active:font-semibold active:bg-[#f1f4f4] dark:active:bg-[#222424] dark:active:text-white",
)}
to={url}
onClick={this.props.toggle.bind(null, false)}
>
{page.title}
</Link>
);
});
}
/**
* Handle clicks on content
*
* @param {object} event - Native click event
*/
_handleBodyClick = (event) => {
const { isOpen, toggle } = this.props;
if (isOpen && this._container && !this._container.contains(event.target)) {
toggle(false);
}
};
_handleTouchStart = (event) => {
this._initialTouchPosition.x = event.touches[0].pageX;
this._initialTouchPosition.y = event.touches[0].pageY;
// For instant transform along with the touch
this._container.classList.add("!duration-0");
};
_handleTouchMove = (event) => {
const xDiff = this._initialTouchPosition.x - event.touches[0].pageX;
const yDiff = this._initialTouchPosition.y - event.touches[0].pageY;
const factor = Math.abs(yDiff / xDiff);
// Factor makes sure horizontal and vertical scroll dont take place together
if (xDiff > 0 && factor < 0.8) {
event.preventDefault();
this._container.style.transform = `translateX(-${xDiff}px)`;
this._lastTouchPosition.x = event.touches[0].pageX;
this._lastTouchPosition.y = event.touches[0].pageY;
}
};
_handleOpenerTouchMove = (event) => {
const xDiff = event.touches[0].pageX - this._initialTouchPosition.x;
const yDiff = this._initialTouchPosition.y - event.touches[0].pageY;
const factor = Math.abs(yDiff / xDiff);
// Factor makes sure horizontal and vertical scroll dont take place together
if (xDiff > 0 && xDiff < 295 && factor < 0.8) {
event.preventDefault();
this._container.style.transform = `translateX(calc(-100% + ${xDiff}px))`;
this._lastTouchPosition.x = event.touches[0].pageX;
this._lastTouchPosition.y = event.touches[0].pageY;
}
};
_handleTouchEnd = (event) => {
const { isOpen } = this.props;
const threshold = 20;
// Free up all the inline styling
this._container.classList.remove("!duration-0");
this._container.style.transform = "";
// are we open?
if (
isOpen &&
this._initialTouchPosition.x - this._lastTouchPosition.x > threshold
) {
// this is in top level nav callback
this.props.toggle(false);
} else if (
!isOpen &&
this._lastTouchPosition.x - this._initialTouchPosition.x > threshold
) {
this.props.toggle(true);
event.preventDefault();
event.stopPropagation();
}
};
}