Skip to content

Commit

Permalink
Hide tabs that don't fit
Browse files Browse the repository at this point in the history
  • Loading branch information
cjmalloy committed Oct 15, 2024
1 parent e799f3d commit ec0b69b
Show file tree
Hide file tree
Showing 6 changed files with 155 additions and 41 deletions.
13 changes: 6 additions & 7 deletions src/app/component/action/action-list/action-list.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
ViewChild,
ViewContainerRef
} from '@angular/core';
import { debounce, defer, delay } from 'lodash-es';
import { defer } from 'lodash-es';
import { Subscription } from 'rxjs';
import { Ref, writeRef } from '../../../model/ref';
import { Action } from '../../../model/tag';
Expand Down Expand Up @@ -83,11 +83,10 @@ export class ActionListComponent implements AfterViewInit {
this.measureVisible();
}

measureVisible = debounce(() => {
measureVisible() {
if (!this.actions) return;
defer(() => this.hiddenActions = this.actions - this.visible);
delay(() => this.hiddenActions = this.actions - this.visible, 500);
}, 400);
this.hiddenActions = this.actions - this.visible;
}

@memo
get actions() {
Expand All @@ -98,8 +97,8 @@ export class ActionListComponent implements AfterViewInit {
get actionWidths() {
const el = this.el.nativeElement;
const result: number[] = [];
for (let i = 0; i < el!.children.length; i++) {
const e = el.parentElement!.children[i] as HTMLElement;
for (let i = 0; i < el.children.length; i++) {
const e = el.children[i] as HTMLElement;
const s = getComputedStyle(e);
result.push(e.offsetWidth + parseInt(s.marginLeft) + parseInt(s.marginRight));
}
Expand Down
7 changes: 4 additions & 3 deletions src/app/component/tabs/tabs.component.html
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
<ng-content></ng-content>
<div *ngIf="options.length > 1"
class="mobile-tab-select">
<div class="mobile-tab-select" [style.display]="hidden ? 'inline-block' : 'none'">
<select #select (input)="nav(select)">
<option i18n>🧭️</option>
<option *ngFor="let opt of options">{{ opt }}</option>
@for (opt of options; track opt) {
<option>{{ opt }}</option>
}
</select>
</div>
<app-settings></app-settings>
134 changes: 125 additions & 9 deletions src/app/component/tabs/tabs.component.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
import { AfterViewInit, Component, ContentChildren, ElementRef, HostBinding, QueryList } from '@angular/core';
import { NavigationEnd, Router, RouterLink } from '@angular/router';
import { filter } from 'rxjs';
import {
AfterViewInit,
Component,
ContentChildren,
ElementRef,
HostBinding,
HostListener,
QueryList
} from '@angular/core';
import { RouterLink } from '@angular/router';
import { defer } from 'lodash-es';
import { ConfigService } from '../../service/config.service';
import { memo, MemoCache } from '../../util/memo';

@Component({
selector: 'app-tabs',
Expand All @@ -17,23 +27,43 @@ export class TabsComponent implements AfterViewInit {

options: string[] = [];
map = new Map<string, number>();
hidden = 0;

private resizeObserver = window.ResizeObserver && new ResizeObserver(() => this.onResize()) || undefined;

constructor(
private router: Router
) {
this.router.events.pipe(
filter(event => event instanceof NavigationEnd)
).subscribe(() => this.updateTabs());
}
private config: ConfigService,
private el: ElementRef<HTMLElement>,
) { }

ngAfterViewInit() {
this.updateTabs();
this.anchors.changes.subscribe(value => {
this.updateTabs();
});
defer(() => this.resizeObserver?.observe(this.el.nativeElement!.parentElement!));
}

@HostBinding('class.floating-tabs')
get floatingTabs() {
return this.config.mini || this.hidden > 0 && this.hidden === this.options.length;
}

@HostListener('window:resize')
onResize() {
if (!this.options.length) return;
this.measureVisible();
}

measureVisible() {
if (!this.options.length) return;
this.hidden = this.options.length - this.visible;
this.hideTabs();
}

updateTabs() {
MemoCache.clear(this);
this.hidden = 0;
this.options = [];
this.map.clear();
const tabs = this.anchors.toArray();
Expand All @@ -45,13 +75,99 @@ export class TabsComponent implements AfterViewInit {
this.options.push(value);
this.map.set(value, tabs.indexOf(t));
}
defer(() => this.onResize());
}

hideTabs() {
const tabs = this.anchors.toArray();
let i = tabs.length - 2;
for (const t of tabs) {
const el = t.nativeElement as HTMLAnchorElement;
if (el.tagName !== 'A') continue;
if (el.classList.contains('logo')) continue;
if (el.classList.contains('current-tab')) {
el.style.display = 'inline-block';
continue;
}
el.style.display = i > this.hidden ? 'inline-block' : 'none';
i--;
}
}

@memo
get tabWidths() {
const result: number[] = [];
const tabs = this.anchors.toArray();
for (const t of tabs) {
const el = t.nativeElement as HTMLAnchorElement;
if (el.tagName !== 'A') continue;
if (el.classList.contains('logo')) continue;
result.push(el.offsetWidth + 10);
}
return result;
}

get currentTabWidth() {
const el = this.el.nativeElement;
for (let i = 0; i < el.children.length; i++) {
const e = el.children[i] as HTMLElement;
if (!e.classList.contains('current-tab')) continue;
return e.offsetWidth + 10;
}
return 0;
}

get childWidths() {
const el = this.el.nativeElement;
const result: number[] = [];
let mobileSelect = false;
for (let i = 0; i < el.children.length; i++) {
const e = el.children[i] as HTMLElement;
if (e.tagName === 'A' && !e.classList.contains('logo')) continue;
if (this.config.mobile) {
if (e.tagName === 'H5') continue;
if (e.classList.contains('logo')) continue;
}
if (e.classList.contains('mobile-tab-select')) mobileSelect = true;
result.push(e.offsetWidth);
}
if (!mobileSelect) {
result.push(52);
}
return result;
}

get visible() {
const current = this.currentTabWidth;
if (!current) return this.options.length;
if (this.floatingTabs) return 0;
const el = this.el.nativeElement;
const width = el.offsetWidth - 16;
let result = 1;
let childWidth = current + this.childWidths.reduce((a, b) => a + b);
if (childWidth > width) return 0;
let skipped = false;
for (const w of this.tabWidths) {
if (!skipped && w === current) {
skipped = true;
continue;
}
childWidth += w;
if (childWidth < width) {
result++;
} else {
return result;
}
}
return this.options.length;
}

nav(select: HTMLSelectElement) {
if (select.value && this.map.has(select.value)) {
this.routerLinks.get(this.map.get(select.value)!)?.onClick(0, false, false, false, false);
}
select.selectedIndex = 0;
this.measureVisible();
}

}
5 changes: 5 additions & 0 deletions src/app/service/config.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export class ConfigService {
*/
prefetch = isDevMode();

miniWidth = 380;
mobileWidth = 740;
tabletWidth = 948;
hugeWidth = 1500;
Expand Down Expand Up @@ -71,6 +72,10 @@ export class ConfigService {
);
}

get mini() {
return window.innerWidth <= this.miniWidth;
}

get mobile() {
return window.innerWidth <= this.mobileWidth;
}
Expand Down
16 changes: 15 additions & 1 deletion src/theme/common.scss
Original file line number Diff line number Diff line change
Expand Up @@ -790,7 +790,7 @@ video.qr-preview {

&.empty > a:not(.logo),
&.empty > app-mobile-tab-select {
mask-image: -webkit-gradient(linear, left top, left bottom, from(rgba(0,0,0,1)), to(rgba(0,0,0,0)));
mask-image: gradient(linear, left top, left bottom, from(rgba(0,0,0,1)), to(rgba(0,0,0,0)));
}

h5 {
Expand All @@ -800,6 +800,19 @@ video.qr-preview {
overflow: hidden;
}

&.floating-tabs {
&>a:not(.logo) {
display: none !important;
}
.current-tab {
display: block;
border: 1px solid var(--border) !important;
margin: 4px;
flex-basis: 100%;
border-radius: 4px;
}
}

.mobile-tab-select {
margin: 0 4px;
select {
Expand Down Expand Up @@ -844,6 +857,7 @@ video.qr-preview {

&.current-tab {
color: var(--active) !important;
display: inline-block !important;
background-color: var(--bg);
border-top: 1px solid var(--border);
border-left: 1px solid var(--border);
Expand Down
21 changes: 0 additions & 21 deletions src/theme/mobile.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,13 @@
$mini-width: 380px;
// Mobile: Change tabs and forms
$mobile-width: 740px;
// Mobile: Change tabs and forms
$settings-tabs-width: 1024px;
// Tablet: Move sidebar above
$tablet-width: 948px;

// TODO: CSS Vars

/* Tablet and Bigger Screens */
@media (min-width: $mobile-width) {
.mobile-tab-select {
display: none;
}

.toggle {
&.actions-toggle, &.view, &.threads, &.comments {
display: none;
Expand Down Expand Up @@ -119,18 +113,6 @@ $tablet-width: 948px;
}
}

/* Special width for settings tabs */
@media (max-width: $settings-tabs-width) {
.settings .tabs {
& > a:not(.current-tab):not(.logo) {
display: none;
}
.mobile-tab-select {
display: block;
}
}
}

/* Phone Screens */
@media (max-width: $mobile-width) {
input[type="color"],
Expand Down Expand Up @@ -188,9 +170,6 @@ $tablet-width: 948px;
h5 {
flex-basis: 100%;
}
& > a:not(.current-tab):not(.logo) {
display: none;
}
}

.editor, .summary-box {
Expand Down

0 comments on commit ec0b69b

Please sign in to comment.