Skip to content

Commit 60fd7bf

Browse files
author
Dobromir Hristov
authored
Allow toggling sidebar navigator on/off, for large breakpoints (#99)
closes rdar://89564104 * feat: allow toggling the navigator on desktop resolutions * feat: allow dragging to close entirely * fix: make sure hidden item is hidden, even for Screen Readers and tab index * fix: add separator to sidebar toggle icon * fix: navigator toggle focus area * fix: navigator closing overflow issues * feat: track container size on screen or sidebar resize * refactor: improve the toggle icon animation * refactor: improvements to the large navigator toggling * fix: issues with body height and weirdly wrapping text * refactor: allow navigator toggle to be shown on small only * refactor: properly add `.sidebar-transitioning` class to the AdjustableSidebarWidth, when in transition. * fix: NavigatorCard.vue close icon focus area * refactor: minor cleanup of constants * fix: quirk in chrome
1 parent e205771 commit 60fd7bf

25 files changed

+826
-197
lines changed

src/components/AdjustableSidebarWidth.vue

Lines changed: 83 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,13 @@
99
-->
1010

1111
<template>
12-
<div class="adjustable-sidebar-width">
12+
<div
13+
class="adjustable-sidebar-width"
14+
:class="{
15+
dragging: isDragging,
16+
'sidebar-hidden': hiddenOnLarge
17+
}"
18+
>
1319
<div
1420
ref="sidebar"
1521
class="sidebar"
@@ -19,8 +25,9 @@
1925
:style="asideStyles"
2026
class="aside"
2127
ref="aside"
22-
@transitionstart="isTransitioning = true"
23-
@transitionend="isTransitioning = false"
28+
:aria-hidden="hiddenOnLarge ? 'true': null"
29+
@transitionstart="trackTransitionStart"
30+
@transitionend="trackTransitionEnd"
2431
>
2532
<slot
2633
name="aside"
@@ -35,7 +42,7 @@
3542
@touchstart.prevent="startDrag"
3643
/>
3744
</div>
38-
<div class="content">
45+
<div class="content" ref="content">
3946
<slot />
4047
</div>
4148
<BreakpointEmitter :scope="BreakpointScopes.nav" @change="breakpoint = $event" />
@@ -96,8 +103,13 @@ export default {
96103
components: {
97104
BreakpointEmitter,
98105
},
106+
inject: ['store'],
99107
props: {
100-
openExternally: {
108+
shownOnMobile: {
109+
type: Boolean,
110+
default: false,
111+
},
112+
hiddenOnLarge: {
101113
type: Boolean,
102114
default: false,
103115
},
@@ -149,12 +161,13 @@ export default {
149161
'--app-height': `${windowHeight}px`,
150162
}),
151163
asideClasses: ({
152-
isDragging, openExternally, noTransition, isTransitioning, mobileTopOffset,
164+
isDragging, shownOnMobile, noTransition, isTransitioning, hiddenOnLarge, mobileTopOffset,
153165
}) => ({
154166
dragging: isDragging,
155-
'force-open': openExternally,
167+
'show-on-mobile': shownOnMobile,
168+
'hide-on-large': hiddenOnLarge,
156169
'no-transition': noTransition,
157-
animating: isTransitioning,
170+
'sidebar-transitioning': isTransitioning,
158171
'has-mobile-top-offset': mobileTopOffset,
159172
}),
160173
scrollLockID: () => SCROLL_LOCK_ID,
@@ -175,7 +188,7 @@ export default {
175188
window.removeEventListener('resize', this.storeWindowSize);
176189
window.removeEventListener('orientationchange', this.storeWindowSize);
177190
window.removeEventListener('scroll', this.storeTopOffset);
178-
if (this.openExternally) {
191+
if (this.shownOnMobile) {
179192
this.toggleScrollLock(false);
180193
}
181194
if (this.focusTrapInstance) this.focusTrapInstance.destroy();
@@ -208,7 +221,13 @@ export default {
208221
// re-apply transitions
209222
this.noTransition = false;
210223
},
211-
openExternally: 'handleExternalOpen',
224+
shownOnMobile: 'handleExternalOpen',
225+
isTransitioning(value) {
226+
if (!value) this.updateContentWidthInStore();
227+
},
228+
hiddenOnLarge() {
229+
this.isTransitioning = true;
230+
},
212231
},
213232
methods: {
214233
getWidthInCheck: debounce(function getWidthInCheck() {
@@ -226,10 +245,11 @@ export default {
226245
await this.$nextTick();
227246
this.windowWidth = window.innerWidth;
228247
this.windowHeight = window.innerHeight;
248+
this.updateContentWidthInStore();
229249
}, 100),
230250
closeMobileSidebar() {
231-
if (!this.openExternally) return;
232-
this.$emit('update:openExternally', false);
251+
if (!this.shownOnMobile) return;
252+
this.$emit('update:shownOnMobile', false);
233253
},
234254
startDrag({ type }) {
235255
this.isTouch = type === 'touchstart';
@@ -254,8 +274,18 @@ export default {
254274
if (newWidth > this.maxWidth) {
255275
newWidth = this.maxWidth;
256276
}
277+
// calculate the forceClose cutoff point
278+
const forceCloseCutoff = this.minWidth / 2;
279+
// if we are going beyond the cutoff point and we are closed, open the navigator
280+
if (this.hiddenOnLarge && newWidth >= forceCloseCutoff) {
281+
this.$emit('update:hiddenOnLarge', false);
282+
}
257283
// prevent from shrinking too much
258284
this.width = Math.max(newWidth, this.minWidth);
285+
// if the new width is smaller than the cutoff point, force close the nav
286+
if (newWidth <= forceCloseCutoff) {
287+
this.$emit('update:hiddenOnLarge', true);
288+
}
259289
},
260290
/**
261291
* Stop the dragging upon mouse up
@@ -273,6 +303,7 @@ export default {
273303
},
274304
emitEventChange(width) {
275305
this.$emit('width-change', width);
306+
this.updateContentWidthInStore();
276307
},
277308
getTopOffset() {
278309
const stickyNavAnchor = document.getElementById(baseNavStickyAnchorId);
@@ -286,6 +317,10 @@ export default {
286317
}
287318
this.toggleScrollLock(isOpen);
288319
},
320+
async updateContentWidthInStore() {
321+
await this.$nextTick();
322+
this.store.setContentWidth(this.$refs.content.offsetWidth);
323+
},
289324
/**
290325
* Toggles the scroll lock on/off
291326
*/
@@ -307,6 +342,16 @@ export default {
307342
storeTopOffset: throttle(function storeTopOffset() {
308343
this.topOffset = this.getTopOffset();
309344
}, 60),
345+
trackTransitionStart({ propertyName }) {
346+
if (propertyName === 'width' || propertyName === 'transform') {
347+
this.isTransitioning = true;
348+
}
349+
},
350+
trackTransitionEnd({ propertyName }) {
351+
if (propertyName === 'width' || propertyName === 'transform') {
352+
this.isTransitioning = false;
353+
}
354+
},
310355
},
311356
};
312357
</script>
@@ -320,6 +365,14 @@ export default {
320365
display: block;
321366
position: relative;
322367
}
368+
369+
&.dragging /deep/ * {
370+
cursor: col-resize !important;
371+
}
372+
373+
&.sidebar-hidden.dragging /deep/ * {
374+
cursor: e-resize !important;
375+
}
323376
}
324377
325378
.sidebar {
@@ -339,6 +392,22 @@ export default {
339392
transition: none !important;
340393
}
341394
395+
@include breakpoints-from(large, nav) {
396+
397+
&:not(.dragging) {
398+
transition: width $adjustable-sidebar-hide-transition-duration ease-in,
399+
visibility 0s linear 0s;
400+
}
401+
402+
&.hide-on-large {
403+
width: 0 !important;
404+
visibility: hidden;
405+
pointer-events: none;
406+
transition: width $adjustable-sidebar-hide-transition-duration ease-in,
407+
visibility 0s linear $adjustable-sidebar-hide-transition-duration;
408+
}
409+
}
410+
342411
@include breakpoint(medium, nav) {
343412
width: 100% !important;
344413
overflow: hidden;
@@ -348,15 +417,15 @@ export default {
348417
position: fixed;
349418
top: var(--top-offset-mobile);
350419
bottom: 0;
351-
z-index: $nav-z-index;
420+
z-index: $nav-z-index + 1;
352421
transform: translateX(-100%);
353422
transition: transform 0.15s ease-in;
354423
355424
/deep/ .aside-animated-child {
356425
opacity: 0;
357426
}
358427
359-
&.force-open {
428+
&.show-on-mobile {
360429
transform: translateX(0);
361430
362431
/deep/ .aside-animated-child {

src/components/DocumentationTopic.vue

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -391,12 +391,6 @@ export default {
391391
outline-style: none;
392392
height: 100%;
393393
394-
@include with-adjustable-sidebar {
395-
@include breakpoints-from(xlarge) {
396-
border-right: 1px solid var(--color-grid);
397-
}
398-
}
399-
400394
@include inTargetIde {
401395
min-height: 100vh;
402396
display: flex;

0 commit comments

Comments
 (0)