mirror of
https://github.com/BookStackApp/BookStack.git
synced 2026-07-16 05:33:49 +03:00
Merge pull request #5685 from BookStackApp/sidebar_rejig
Tri-layout sidebar enhancements
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import {onSelect} from '../services/dom.ts';
|
||||
import {findClosestScrollContainer, onSelect} from '../services/dom.ts';
|
||||
import {KeyboardNavigationHandler} from '../services/keyboard-navigation.ts';
|
||||
import {Component} from './component';
|
||||
|
||||
@@ -33,7 +33,8 @@ export class Dropdown extends Component {
|
||||
const menuOriginalRect = this.menu.getBoundingClientRect();
|
||||
let heightOffset = 0;
|
||||
const toggleHeight = this.toggle.getBoundingClientRect().height;
|
||||
const dropUpwards = menuOriginalRect.bottom > window.innerHeight;
|
||||
const containerBounds = findClosestScrollContainer(this.menu).getBoundingClientRect();
|
||||
const dropUpwards = menuOriginalRect.bottom > containerBounds.bottom;
|
||||
const containerRect = this.container.getBoundingClientRect();
|
||||
|
||||
// If enabled, Move to body to prevent being trapped within scrollable sections
|
||||
|
||||
@@ -1,18 +1,22 @@
|
||||
import {Component} from './component';
|
||||
|
||||
export class TriLayout extends Component {
|
||||
private container!: HTMLElement;
|
||||
private tabs!: HTMLElement[];
|
||||
private sidebarScrollContainers!: HTMLElement[];
|
||||
|
||||
setup() {
|
||||
private lastLayoutType = 'none';
|
||||
private onDestroy: (()=>void)|null = null;
|
||||
private scrollCache: Record<string, number> = {
|
||||
content: 0,
|
||||
info: 0,
|
||||
};
|
||||
private lastTabShown = 'content';
|
||||
|
||||
setup(): void {
|
||||
this.container = this.$refs.container;
|
||||
this.tabs = this.$manyRefs.tab;
|
||||
|
||||
this.lastLayoutType = 'none';
|
||||
this.onDestroy = null;
|
||||
this.scrollCache = {
|
||||
content: 0,
|
||||
info: 0,
|
||||
};
|
||||
this.lastTabShown = 'content';
|
||||
this.sidebarScrollContainers = this.$manyRefs.sidebarScrollContainer;
|
||||
|
||||
// Bind any listeners
|
||||
this.mobileTabClick = this.mobileTabClick.bind(this);
|
||||
@@ -22,9 +26,11 @@ export class TriLayout extends Component {
|
||||
window.addEventListener('resize', () => {
|
||||
this.updateLayout();
|
||||
}, {passive: true});
|
||||
|
||||
this.setupSidebarScrollHandlers();
|
||||
}
|
||||
|
||||
updateLayout() {
|
||||
updateLayout(): void {
|
||||
let newLayout = 'tablet';
|
||||
if (window.innerWidth <= 1000) newLayout = 'mobile';
|
||||
if (window.innerWidth > 1400) newLayout = 'desktop';
|
||||
@@ -56,16 +62,15 @@ export class TriLayout extends Component {
|
||||
};
|
||||
}
|
||||
|
||||
setupDesktop() {
|
||||
setupDesktop(): void {
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Action to run when the mobile info toggle bar is clicked/tapped
|
||||
* @param event
|
||||
*/
|
||||
mobileTabClick(event) {
|
||||
const {tab} = event.target.dataset;
|
||||
mobileTabClick(event: MouseEvent): void {
|
||||
const tab = (event.target as HTMLElement).dataset.tab || '';
|
||||
this.showTab(tab);
|
||||
}
|
||||
|
||||
@@ -73,16 +78,14 @@ export class TriLayout extends Component {
|
||||
* Show the content tab.
|
||||
* Used by the page-display component.
|
||||
*/
|
||||
showContent() {
|
||||
showContent(): void {
|
||||
this.showTab('content', false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the given tab
|
||||
* @param {String} tabName
|
||||
* @param {Boolean }scroll
|
||||
*/
|
||||
showTab(tabName, scroll = true) {
|
||||
showTab(tabName: string, scroll: boolean = true): void {
|
||||
this.scrollCache[this.lastTabShown] = document.documentElement.scrollTop;
|
||||
|
||||
// Set tab status
|
||||
@@ -97,7 +100,7 @@ export class TriLayout extends Component {
|
||||
|
||||
// Set the scroll position from cache
|
||||
if (scroll) {
|
||||
const pageHeader = document.querySelector('header');
|
||||
const pageHeader = document.querySelector('header') as HTMLElement;
|
||||
const defaultScrollTop = pageHeader.getBoundingClientRect().bottom;
|
||||
document.documentElement.scrollTop = this.scrollCache[tabName] || defaultScrollTop;
|
||||
setTimeout(() => {
|
||||
@@ -108,4 +111,30 @@ export class TriLayout extends Component {
|
||||
this.lastTabShown = tabName;
|
||||
}
|
||||
|
||||
setupSidebarScrollHandlers(): void {
|
||||
for (const sidebar of this.sidebarScrollContainers) {
|
||||
sidebar.addEventListener('scroll', () => this.handleSidebarScroll(sidebar), {
|
||||
passive: true,
|
||||
});
|
||||
this.handleSidebarScroll(sidebar);
|
||||
}
|
||||
|
||||
window.addEventListener('resize', () => {
|
||||
for (const sidebar of this.sidebarScrollContainers) {
|
||||
this.handleSidebarScroll(sidebar);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
handleSidebarScroll(sidebar: HTMLElement): void {
|
||||
const scrollable = sidebar.clientHeight !== sidebar.scrollHeight;
|
||||
const atTop = sidebar.scrollTop === 0;
|
||||
const atBottom = (sidebar.scrollTop + sidebar.clientHeight) === sidebar.scrollHeight;
|
||||
|
||||
if (sidebar.parentElement) {
|
||||
sidebar.parentElement.classList.toggle('scroll-away-from-top', !atTop && scrollable);
|
||||
sidebar.parentElement.classList.toggle('scroll-away-from-bottom', !atBottom && scrollable);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -256,4 +256,22 @@ export function findTargetNodeAndOffset(parentNode: HTMLElement, offset: number)
|
||||
export function hashElement(element: HTMLElement): string {
|
||||
const normalisedElemText = (element.textContent || '').replace(/\s{2,}/g, '');
|
||||
return cyrb53(normalisedElemText);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the closest scroll container parent for the given element
|
||||
* otherwise will default to the body element.
|
||||
*/
|
||||
export function findClosestScrollContainer(start: HTMLElement): HTMLElement {
|
||||
let el: HTMLElement|null = start;
|
||||
do {
|
||||
const computed = window.getComputedStyle(el);
|
||||
if (computed.overflowY === 'scroll') {
|
||||
return el;
|
||||
}
|
||||
|
||||
el = el.parentElement;
|
||||
} while (el);
|
||||
|
||||
return document.body;
|
||||
}
|
||||
@@ -389,10 +389,12 @@ body.flexbox {
|
||||
.tri-layout-right {
|
||||
grid-area: c;
|
||||
min-width: 0;
|
||||
position: relative;
|
||||
}
|
||||
.tri-layout-left {
|
||||
grid-area: a;
|
||||
min-width: 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@include mixins.larger-than(vars.$bp-xxl) {
|
||||
@@ -431,7 +433,8 @@ body.flexbox {
|
||||
grid-template-areas: "a b b";
|
||||
grid-template-columns: 1fr 3fr;
|
||||
grid-template-rows: min-content min-content 1fr;
|
||||
padding-inline-end: vars.$l;
|
||||
margin-inline-start: (vars.$m + vars.$xxs);
|
||||
margin-inline-end: (vars.$m + vars.$xxs);
|
||||
}
|
||||
.tri-layout-sides {
|
||||
grid-column-start: a;
|
||||
@@ -452,6 +455,8 @@ body.flexbox {
|
||||
height: 100%;
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
padding-inline: vars.$m;
|
||||
margin-inline: -(vars.$m);
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
@@ -520,4 +525,26 @@ body.flexbox {
|
||||
margin-inline-start: 0;
|
||||
margin-inline-end: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scroll Indicators
|
||||
*/
|
||||
.scroll-away-from-top:before,
|
||||
.scroll-away-from-bottom:after {
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
@include mixins.lightDark(color, #F2F2F2, #111);
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
background: linear-gradient(to bottom, currentColor, transparent);
|
||||
z-index: 2;
|
||||
}
|
||||
.scroll-away-from-bottom:after {
|
||||
top: auto;
|
||||
bottom: 0;
|
||||
background: linear-gradient(to top, currentColor, transparent);
|
||||
}
|
||||
@@ -28,15 +28,15 @@
|
||||
<div refs="tri-layout@container" class="tri-layout-container" @yield('container-attrs') >
|
||||
|
||||
<div class="tri-layout-sides print-hidden">
|
||||
<div class="tri-layout-sides-content">
|
||||
<div refs="tri-layout@sidebar-scroll-container" class="tri-layout-sides-content">
|
||||
<div class="tri-layout-right print-hidden">
|
||||
<aside class="tri-layout-right-contents">
|
||||
<aside refs="tri-layout@sidebar-scroll-container" class="tri-layout-right-contents">
|
||||
@yield('right')
|
||||
</aside>
|
||||
</div>
|
||||
|
||||
<div class="tri-layout-left print-hidden" id="sidebar">
|
||||
<aside class="tri-layout-left-contents">
|
||||
<aside refs="tri-layout@sidebar-scroll-container" class="tri-layout-left-contents">
|
||||
@yield('left')
|
||||
</aside>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user