]> BookStack Code Mirror - bookstack/commitdiff
Image manager: supported a tabbed interface on mobile
authorDan Brown <redacted>
Sat, 27 May 2023 15:58:10 +0000 (16:58 +0100)
committerDan Brown <redacted>
Sat, 27 May 2023 15:58:10 +0000 (16:58 +0100)
Makes interface relatively usable now on mobile sizes.
Required updating of tab handling to support tabs being active at only
mobile screen sizes, include change on resize, upon support for
potentially nested tab usage.
Tab component will now search within sensible depths for finding its own
tabs and panels to control.

resources/js/components/tabs.js
resources/sass/_components.scss
resources/sass/_layout.scss
resources/views/pages/parts/image-manager.blade.php

index 560dc6273e3937eeb0744c3b8ae9210eb9bb6d92..c3788c74795018acd4090571c0a4e1abcb032cb4 100644 (file)
@@ -21,15 +21,23 @@ export class Tabs extends Component {
 
     setup() {
         this.container = this.$el;
-        this.tabs = Array.from(this.container.querySelectorAll('[role="tab"]'));
-        this.panels = Array.from(this.container.querySelectorAll('[role="tabpanel"]'));
+        this.tabList = this.container.querySelector('[role="tablist"]');
+        this.tabs = Array.from(this.tabList.querySelectorAll('[role="tab"]'));
+        this.panels = Array.from(this.container.querySelectorAll(':scope > [role="tabpanel"], :scope > * > [role="tabpanel"]'));
+        this.activeUnder = this.$opts.activeUnder ? Number(this.$opts.activeUnder) : 10000;
+        this.active = null;
 
         this.container.addEventListener('click', event => {
-            const button = event.target.closest('[role="tab"]');
-            if (button) {
-                this.show(button.getAttribute('aria-controls'));
+            const tab = event.target.closest('[role="tab"]');
+            if (tab && this.tabs.includes(tab)) {
+                this.show(tab.getAttribute('aria-controls'));
             }
         });
+
+        window.addEventListener('resize', this.updateActiveState.bind(this), {
+            passive: true,
+        });
+        this.updateActiveState();
     }
 
     show(sectionId) {
@@ -46,4 +54,34 @@ export class Tabs extends Component {
         this.$emit('change', {showing: sectionId});
     }
 
+    updateActiveState() {
+        const active = window.innerWidth < this.activeUnder;
+        if (active === this.active) {
+            return;
+        }
+
+        if (active) {
+            this.activate();
+        } else {
+            this.deactivate();
+        }
+
+        this.active = active;
+    }
+
+    activate() {
+        this.show(this.panels[0].id);
+        this.tabList.toggleAttribute('hidden', false);
+    }
+
+    deactivate() {
+        for (const panel of this.panels) {
+            panel.removeAttribute('hidden');
+        }
+        for (const tab of this.tabs) {
+            tab.setAttribute('aria-selected', 'false');
+        }
+        this.tabList.toggleAttribute('hidden', true);
+    }
+
 }
index 51d95236e2eec23ef91c48003a364b66811fb0b2..1a3e1669ef7713e8335f1fbf8b2db060d01ba129 100644 (file)
@@ -380,6 +380,12 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group {
     background-color: #FFF;
   }
 }
+
+@include smaller-than($s) {
+  .image-manager-filter-bar .contained-search-box input {
+    width: 180px;
+  }
+}
 .image-manager-filters {
   box-shadow: $bs-med;
   border-radius: 4px;
@@ -475,6 +481,7 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group {
 
 .image-manager-sidebar {
   width: 300px;
+  margin: 0 auto;
   overflow-y: auto;
   overflow-x: hidden;
   border-inline-start: 1px solid #DDD;
@@ -500,6 +507,11 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group {
     }
   }
 }
+@include smaller-than($m) {
+  .image-manager-sidebar {
+    border-inline-start: 0;
+  }
+}
 
 .image-manager-content {
   display: flex;
index 541978a65e2ac14aba3d34ee1624c3a6afcca849..11889da172239b0ef346ab93d298729d86d36555 100644 (file)
@@ -298,6 +298,10 @@ body.flexbox {
   }
 }
 
+[hidden] {
+  display: none !important;
+}
+
 /**
  * Border radiuses
  */
index 53a361c27cde4944fb0f60f9cf6e8c881880afbe..9945f4d6c6dbdc02c385cf0d0446bb78535f5557 100644 (file)
                 <button refs="popup@hide" type="button" class="popup-header-close">@icon('close')</button>
             </div>
 
-            <div refs="dropzone@drop-target" class="flex-fill image-manager-body">
-                <div class="image-manager-content">
-                    <div class="image-manager-filter-bar flex-container-row justify-space-between">
-                        <div class="primary-background image-manager-filter-bar-bg"></div>
-                        <div>
-                            <form refs="image-manager@searchForm" class="contained-search-box">
-                                <input refs="image-manager@searchInput"
-                                       placeholder="{{ trans('components.image_search_hint') }}"
-                                       type="text">
-                                <button refs="image-manager@cancelSearch"
-                                        title="{{ trans('common.search_clear') }}"
-                                        type="button"
-                                        class="cancel">@icon('close')</button>
-                                <button type="submit"
-                                        title="{{ trans('common.search') }}">@icon('search')</button>
-                            </form>
-                        </div>
-                        <div class="tab-container bordered tab-primary">
-                            <div role="tablist" class="image-manager-filters flex-container-row">
-                                <button refs="image-manager@filterTabs"
-                                        data-filter="all"
-                                        role="tab"
-                                        aria-selected="true"
-                                        type="button"
-                                        title="{{ trans('components.image_all_title') }}">@icon('images')</button>
-                                <button refs="image-manager@filterTabs"
-                                        data-filter="book"
-                                        role="tab"
-                                        aria-selected="false"
-                                        type="button"
-                                        title="{{ trans('components.image_book_title') }}">@icon('book', ['class' => 'svg-icon'])</button>
-                                <button refs="image-manager@filterTabs"
-                                        data-filter="page"
-                                        role="tab"
-                                        aria-selected="false"
-                                        type="button"
-                                        title="{{ trans('components.image_page_title') }}">@icon('page', ['class' => 'svg-icon'])</button>
+            <div component="tabs"
+                 option:tabs:active-under="880"
+                 refs="dropzone@drop-target"
+                 class="flex-container-column image-manager-body">
+                <div class="tab-container">
+                    <div role="tablist" class="hide-over-m mb-none">
+                        <button id="image-manager-list-tab"
+                                aria-selected="true"
+                                aria-controls="image-manager-list"
+                                role="tab">Image List</button>
+                        <button id="image-manager-info-tab"
+                                aria-selected="true"
+                                aria-controls="image-manager-info"
+                                role="tab">Image Details</button>
+                    </div>
+                </div>
+                <div class="flex-container-row flex-fill">
+                    <div id="image-manager-list"
+                         tabindex="0"
+                         role="tabpanel"
+                         aria-labelledby="image-manager-list-tab"
+                         class="image-manager-content">
+                        <div class="image-manager-filter-bar flex-container-row wrap justify-space-between">
+                            <div class="primary-background image-manager-filter-bar-bg"></div>
+                            <div>
+                                <form refs="image-manager@searchForm" class="contained-search-box">
+                                    <input refs="image-manager@searchInput"
+                                           placeholder="{{ trans('components.image_search_hint') }}"
+                                           type="text">
+                                    <button refs="image-manager@cancelSearch"
+                                            title="{{ trans('common.search_clear') }}"
+                                            type="button"
+                                            class="cancel">@icon('close')</button>
+                                    <button type="submit"
+                                            title="{{ trans('common.search') }}">@icon('search')</button>
+                                </form>
+                            </div>
+                            <div class="tab-container bordered tab-primary">
+                                <div role="tablist" class="image-manager-filters flex-container-row">
+                                    <button refs="image-manager@filterTabs"
+                                            data-filter="all"
+                                            role="tab"
+                                            aria-selected="true"
+                                            type="button"
+                                            title="{{ trans('components.image_all_title') }}">@icon('images')</button>
+                                    <button refs="image-manager@filterTabs"
+                                            data-filter="book"
+                                            role="tab"
+                                            aria-selected="false"
+                                            type="button"
+                                            title="{{ trans('components.image_book_title') }}">@icon('book', ['class' => 'svg-icon'])</button>
+                                    <button refs="image-manager@filterTabs"
+                                            data-filter="page"
+                                            role="tab"
+                                            aria-selected="false"
+                                            type="button"
+                                            title="{{ trans('components.image_page_title') }}">@icon('page', ['class' => 'svg-icon'])</button>
+                                </div>
                             </div>
                         </div>
+                        <div refs="image-manager@listContainer" class="image-manager-list"></div>
+                        <div refs="image-manager@loadMore" class="load-more" hidden>
+                            <button type="button" class="button small outline">Load More</button>
+                        </div>
                     </div>
-                    <div refs="image-manager@listContainer" class="image-manager-list"></div>
-                    <div refs="image-manager@loadMore" class="load-more" hidden>
-                        <button type="button" class="button small outline">Load More</button>
-                    </div>
-                </div>
 
-                <div class="image-manager-sidebar flex-container-column">
+                    <div id="image-manager-info"
+                         tabindex="0"
+                         role="tabpanel"
+                         aria-labelledby="image-manager-info-tab"
+                         class="image-manager-sidebar flex-container-column">
 
-                    <div refs="image-manager@dropzoneContainer">
-                        <div refs="dropzone@status-area"></div>
-                    </div>
+                        <div refs="image-manager@dropzoneContainer">
+                            <div refs="dropzone@status-area"></div>
+                        </div>
 
-                    <div refs="image-manager@form-container-placeholder" class="p-m text-small text-muted">
-                        <p>{{ trans('components.image_intro') }}</p>
-                        <p refs="image-manager@upload-hint">{{ trans('components.image_intro_upload') }}</p>
-                    </div>
+                        <div refs="image-manager@form-container-placeholder" class="p-m text-small text-muted">
+                            <p>{{ trans('components.image_intro') }}</p>
+                            <p refs="image-manager@upload-hint">{{ trans('components.image_intro_upload') }}</p>
+                        </div>
 
-                    <div refs="image-manager@formContainer" class="inner flex">
+                        <div refs="image-manager@formContainer" class="inner flex">
+                        </div>
                     </div>
                 </div>
-
             </div>
 
             <div class="popup-footer">
Morty Proxy This is a proxified and sanitized view of the page, visit original site.