Bugzilla – Attachment 194355 Details for
Bug 41963
OPAC list view toolbar and action buttons consume most of the screen on mobile
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Help
|
New Account
|
Log In
[x]
|
Forgot Password
Login:
[x]
[patch]
Bug 41963: Improve OPAC list view mobile responsiveness
Bug-41963-Improve-OPAC-list-view-mobile-responsive.patch (text/plain), 31.17 KB, created by
Paul Derscheid
on 2026-03-02 18:56:45 UTC
(
hide
)
Description:
Bug 41963: Improve OPAC list view mobile responsiveness
Filename:
MIME Type:
Creator:
Paul Derscheid
Created:
2026-03-02 18:56:45 UTC
Size:
31.17 KB
patch
obsolete
>From f9b7f975691d4241dff09717e4d0c1bb8d6355dc Mon Sep 17 00:00:00 2001 >From: Paul Derscheid <paul.derscheid@lmscloud.de> >Date: Mon, 2 Mar 2026 13:15:58 +0100 >Subject: [PATCH] Bug 41963: Improve OPAC list view mobile responsiveness > >- On mobile devices, the OPAC virtual shelves/lists view has the > toolbar and selections bar permanently expanded and sticky, > consuming most of the viewport before any list content is visible >- Per-item action buttons render as full-width block elements with > 120% font size, taking more space than the content itself >- Add collapsible toolbar on mobile (<=608px) with a toggle bar, > collapsed by default so list content is visible immediately >- Make per-item action buttons compact inline links instead of > full-width block buttons on the shelves page >- Scope all responsive overrides to #opac-userlists/#opac-lists > body IDs to avoid side effects on other OPAC pages >- Add aria-expanded and aria-controls for accessibility > >Test plan: >- Go to the OPAC, create or open a list with several items >- Resize the browser to a mobile viewport (<=608px) or use a phone >- Observe the toolbar takes up most of the screen and cannot be > collapsed, leaving little room for actual list content >- Observe per-item action buttons are oversized full-width blocks >- Apply patch >- Run: yarn css:build >- Run: koha-plack --reload kohadev >- Reload the list page at the same mobile viewport >- Verify the toolbar is now collapsed behind a "List actions" bar >- Click the toggle to expand the toolbar, verify all actions are > still accessible, click again to collapse >- Verify per-item actions display as compact inline links >- Resize to desktop width (>608px) and verify the toolbar displays > normally with no toggle button visible >- Verify aria-expanded attribute changes on toggle click >--- > .../bootstrap/css/src/_responsive.scss | 39 +++ > .../bootstrap/en/modules/opac-shelves.tt | 284 +++++++++--------- > 2 files changed, 189 insertions(+), 134 deletions(-) > >diff --git a/koha-tmpl/opac-tmpl/bootstrap/css/src/_responsive.scss b/koha-tmpl/opac-tmpl/bootstrap/css/src/_responsive.scss >index 04a84b18938..684391159b6 100644 >--- a/koha-tmpl/opac-tmpl/bootstrap/css/src/_responsive.scss >+++ b/koha-tmpl/opac-tmpl/bootstrap/css/src/_responsive.scss >@@ -217,6 +217,45 @@ > #checkouts_filter { > display: block; > } >+ >+ #opac-userlists, >+ #opac-lists { >+ .toolbar-collapse-toggle { >+ background-color: #EEEEEE; >+ color: $gray-550; >+ display: block !important; >+ font-size: .9rem; >+ padding: 6px 10px; >+ text-align: center; >+ width: 100%; >+ >+ &:hover, >+ &:focus { >+ background-color: darken( #EEEEEE, 5% ); >+ color: $gray-700; >+ text-decoration: none; >+ } >+ } >+ >+ .toolbar-collapse { >+ display: none; >+ >+ &.open { >+ display: block; >+ } >+ } >+ >+ .actions-menu .actions { >+ display: inline; >+ >+ a { >+ display: inline; >+ font-size: .9rem; >+ font-weight: normal; >+ margin: 0; >+ } >+ } >+ } > } > > @media only screen and ( max-width: 700px ) { >diff --git a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-shelves.tt b/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-shelves.tt >index 78629f1e38a..acbe6ddcc4a 100644 >--- a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-shelves.tt >+++ b/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-shelves.tt >@@ -258,149 +258,155 @@ > [% SET contents = shelf.get_contents %] > [% IF ( contents.count ) %]<p>This list contains [% contents.count | html %] titles</p>[% END %] > <div id="floating" class="sticky"> >- <div id="toolbar" class="toolbar clearfix"> >- <div class="list-actions"> >- <a class="btn btn-link newshelf" href="/cgi-bin/koha/opac-shelves.pl?op=add_form"><i class="fa fa-fw fa-plus" aria-hidden="true"></i> New list</a> <span class="sep">|</span> >- >- <div id="download-list" class="btn-group dropdown"> >- <a >- id="format" >- class="btn btn-link dropdown-toggle" >- aria-haspopup="menu" >- aria-label="Select format and download list" >- data-bs-toggle="dropdown" >- href="/cgi-bin/koha/opac-downloadshelf.pl?shelfnumber=[% shelf.shelfnumber | html %]" >- ><i class="fa fa-fw fa-download" aria-hidden="true"></i> Download <b class="caret"></b >- ></a> >- <div class="dropdown-menu pull-left" role="menu" aria-labelledby="format"> >- <a role="menuitem" class="dropdown-item download-list" data-format="bibtex" href="#">BibTeX</a> >- [% IF Koha.Preference('OPACISBD') %]<a role="menuitem" class="dropdown-item download-list" data-format="isbd" href="#">ISBD</a>[% END %] >- <a role="menuitem" class="dropdown-item download-list" data-format="iso2709" href="#">MARC</a> >- <a role="menuitem" class="dropdown-item download-list" data-format="ris" href="#">RIS (Zotero, EndNote, others)</a> >- [% FOREACH csv_profile IN csv_profiles %] >- <a role="menuitem" class="dropdown-item download-list" data-format="[% csv_profile.export_format_id | html %]" href="#">CSV - [% csv_profile.profile | html %]</a> >- [% END %] >+ <a href="#" class="toolbar-collapse-toggle d-none" aria-expanded="false" aria-controls="toolbar-collapse-content"> <i class="fa fa-bars" aria-hidden="true"></i> List actions </a> >+ <div class="toolbar-collapse" id="toolbar-collapse-content"> >+ <div id="toolbar" class="toolbar clearfix"> >+ <div class="list-actions"> >+ <a class="btn btn-link newshelf" href="/cgi-bin/koha/opac-shelves.pl?op=add_form"><i class="fa fa-fw fa-plus" aria-hidden="true"></i> New list</a> <span class="sep">|</span> >+ >+ <div id="download-list" class="btn-group dropdown"> >+ <a >+ id="format" >+ class="btn btn-link dropdown-toggle" >+ aria-haspopup="menu" >+ aria-label="Select format and download list" >+ data-bs-toggle="dropdown" >+ href="/cgi-bin/koha/opac-downloadshelf.pl?shelfnumber=[% shelf.shelfnumber | html %]" >+ ><i class="fa fa-fw fa-download" aria-hidden="true"></i> Download <b class="caret"></b >+ ></a> >+ <div class="dropdown-menu pull-left" role="menu" aria-labelledby="format"> >+ <a role="menuitem" class="dropdown-item download-list" data-format="bibtex" href="#">BibTeX</a> >+ [% IF Koha.Preference('OPACISBD') %]<a role="menuitem" class="dropdown-item download-list" data-format="isbd" href="#">ISBD</a>[% END %] >+ <a role="menuitem" class="dropdown-item download-list" data-format="iso2709" href="#">MARC</a> >+ <a role="menuitem" class="dropdown-item download-list" data-format="ris" href="#">RIS (Zotero, EndNote, others)</a> >+ [% FOREACH csv_profile IN csv_profiles %] >+ <a role="menuitem" class="dropdown-item download-list" data-format="[% csv_profile.export_format_id | html %]" href="#">CSV - [% csv_profile.profile | html %]</a> >+ [% END %] >+ </div> > </div> >- </div> > >- [% IF ( Koha.Preference( 'opacuserlogin' ) == 1 ) && ( ! Koha.Preference('OPACDisableSendList') ) %] >- <span class="sendlist" >- ><a href="/cgi-bin/koha/opac-sendshelf.pl?shelfid=[% shelf.shelfnumber | uri %]" class="btn btn-link send"><i class="fa fa-fw fa-envelope" aria-hidden="true"></i> Send list</a></span >+ [% IF ( Koha.Preference( 'opacuserlogin' ) == 1 ) && ( ! Koha.Preference('OPACDisableSendList') ) %] >+ <span class="sendlist" >+ ><a href="/cgi-bin/koha/opac-sendshelf.pl?shelfid=[% shelf.shelfnumber | uri %]" class="btn btn-link send"><i class="fa fa-fw fa-envelope" aria-hidden="true"></i> Send list</a></span >+ > >+ [% END %] >+ >+ <a >+ class="btn btn-link print-small" >+ target="_blank" >+ href="/cgi-bin/koha/opac-shelves.pl?op=view&shelfnumber=[% shelf.shelfnumber | html %]&sortfield=[% sortfield | html %]&direction=[% direction | uri %]&print=1" >+ ><i class="fa fa-fw fa-print" aria-hidden="true"></i> Print list</a > > >- [% END %] > >- <a >- class="btn btn-link print-small" >- target="_blank" >- href="/cgi-bin/koha/opac-shelves.pl?op=view&shelfnumber=[% shelf.shelfnumber | html %]&sortfield=[% sortfield | html %]&direction=[% direction | uri %]&print=1" >- ><i class="fa fa-fw fa-print" aria-hidden="true"></i> Print list</a >- > >+ [% IF can_manage_shelf %] >+ <span class="sep">|</span> >+ <form method="get" action="/cgi-bin/koha/opac-shelves.pl" class="d-inline"> >+ <input type="hidden" name="op" value="edit_form" /> >+ <input type="hidden" name="referer" value="view" /> >+ <input type="hidden" name="public" value="[% shelf.public | html %]" /> >+ <input type="hidden" name="shelfnumber" value="[% shelf.shelfnumber | html %]" /> >+ <button type="submit" class="btn btn-link editshelf"><i class="fa-fw fa-solid fa-pencil" aria-hidden="true"></i> Edit list</button> >+ </form> >+ >+ [% PROCESS delete_shelf context = "details" %] >+ >+ [% IF !public && Koha.Preference('OpacAllowSharingPrivateLists') %] >+ <a href="/cgi-bin/koha/opac-shareshelf.pl?op=show&shelfnumber=[% shelf.shelfnumber | uri %]" class="btn btn-link sharelist" >+ ><i class="fa fa-fw fa-share" aria-hidden="true"></i> Share list</a >+ > >+ [% END %] >+ [% ELSIF !public # not manageshelf and private means shared %] >+ <form action="/cgi-bin/koha/opac-shelves.pl" method="post" id="unshare[% shelf.shelfnumber | html %]" class="d-inline"> >+ [% INCLUDE 'csrf-token.inc' %] >+ <input type="hidden" name="op" value="cud-remove_share" /> >+ <input type="hidden" name="referer" value="list" /> >+ <input type="hidden" name="public" value="[% public | html %]" /> >+ <input type="hidden" name="shelfnumber" value="[% shelf.shelfnumber | html %]" /> >+ <button type="submit" class="btn btn-link remove remove_share" data-shelfname="[% shelf.shelfname | html %]" data-shelfnumber="[% shelf.shelfnumber | html %]"> >+ <i class="fa fa-times" aria-hidden="true"></i> Remove share >+ </button> >+ </form> >+ [% END # /IF can_manage_shelf %] >+ </div> >+ <!-- /.list-actions --> > >- [% IF can_manage_shelf %] >- <span class="sep">|</span> >- <form method="get" action="/cgi-bin/koha/opac-shelves.pl" class="d-inline"> >- <input type="hidden" name="op" value="edit_form" /> >- <input type="hidden" name="referer" value="view" /> >- <input type="hidden" name="public" value="[% shelf.public | html %]" /> >- <input type="hidden" name="shelfnumber" value="[% shelf.shelfnumber | html %]" /> >- <button type="submit" class="btn btn-link editshelf"><i class="fa-fw fa-solid fa-pencil" aria-hidden="true"></i> Edit list</button> >- </form> >+ <form method="get" action="/cgi-bin/koha/opac-shelves.pl" id="sorting-form" class="d-inline sort_by"> >+ <input type="hidden" name="op" value="view" /> >+ <input type="hidden" name="shelfnumber" value="[% shelf.shelfnumber | html %]" /> > >- [% PROCESS delete_shelf context = "details" %] >+ <label for="sortfield">Sort by: </label> >+ <select name="sortfield" id="sortfield" class="resort"> >+ <option value="">Default sorting</option> >+ <optgroup label="Author"> >+ [% IF sortfield == 'author' && direction == 'asc' %] >+ <option value="author" data-direction="asc" selected="selected">Author (A-Z)</option> >+ [% ELSE %] >+ <option value="author" data-direction="asc">Author (A-Z)</option> >+ [% END %] >+ [% IF sortfield == 'author' && direction == 'desc' %] >+ <option value="author" data-direction="desc" selected="selected">Author (Z-A)</option> >+ [% ELSE %] >+ <option value="author" data-direction="desc">Author (Z-A)</option> >+ [% END %] >+ </optgroup> >+ <optgroup label="Title"> >+ [% IF sortfield == 'title' && direction == 'asc' %] >+ <option value="title" data-direction="asc" selected="selected">Title (A-Z)</option> >+ [% ELSE %] >+ <option value="title" data-direction="asc">Title (A-Z)</option> >+ [% END %] >+ [% IF sortfield == 'title' && direction == 'desc' %] >+ <option value="title" data-direction="desc" selected="selected">Title (Z-A)</option> >+ [% ELSE %] >+ <option value="title" data-direction="desc">Title (Z-A)</option> >+ [% END %] >+ </optgroup> >+ <optgroup label="Call number"> >+ [% IF sortfield == 'itemcallnumber' && direction == 'asc' %] >+ <option value="itemcallnumber" data-direction="asc" selected="selected">Call number (A-Z)</option> >+ [% ELSE %] >+ <option value="itemcallnumber" data-direction="asc">Call number (A-Z)</option> >+ [% END %] >+ [% IF sortfield == 'itemcallnumber' && direction == 'desc' %] >+ <option value="itemcallnumber" data-direction="desc" selected="selected">Call number (Z-A)</option> >+ [% ELSE %] >+ <option value="itemcallnumber" data-direction="desc">Call number (Z-A)</option> >+ [% END %] >+ </optgroup> >+ <optgroup label="Copyright date"> >+ [% IF sortfield == 'copyrightdate' && direction == 'asc' %] >+ <option value="copyrightdate" data-direction="desc" selected="selected">Copyright date (newest to oldest)</option> >+ [% ELSE %] >+ <option value="copyrightdate" data-direction="desc">Copyright date (newest to oldest)</option> >+ [% END %] >+ [% IF sortfield == 'copyrightdate' && direction == 'desc' %] >+ <option value="copyrightdate" data-direction="asc" selected="selected">Copyright date (oldest to newest)</option> >+ [% ELSE %] >+ <option value="copyrightdate" data-direction="asc">Copyright date (oldest to newest)</option> >+ [% END %] >+ </optgroup> >+ <optgroup label="Date added"> >+ [% IF sortfield == "dateadded" && direction == 'asc' %] >+ <option value="dateadded" data-direction="desc" selected="selected">Date added (newest to oldest)</option> >+ [% ELSE %] >+ <option value="dateadded" data-direction="desc">Date added (newest to oldest)</option> >+ [% END %] >+ [% IF sortfield == "dateadded" && direction == 'desc' %] >+ <option value="dateadded" data-direction="asc" selected="selected">Date added (oldest to newest)</option> >+ [% ELSE %] >+ <option value="dateadded" data-direction="asc">Date added (oldest to newest)</option> >+ [% END %] >+ </optgroup> >+ </select> > >- [% IF !public && Koha.Preference('OpacAllowSharingPrivateLists') %] >- <a href="/cgi-bin/koha/opac-shareshelf.pl?op=show&shelfnumber=[% shelf.shelfnumber | uri %]" class="btn btn-link sharelist"><i class="fa fa-fw fa-share" aria-hidden="true"></i> Share list</a> >- [% END %] >- [% ELSIF !public # not manageshelf and private means shared %] >- <form action="/cgi-bin/koha/opac-shelves.pl" method="post" id="unshare[% shelf.shelfnumber | html %]" class="d-inline"> >- [% INCLUDE 'csrf-token.inc' %] >- <input type="hidden" name="op" value="cud-remove_share" /> >- <input type="hidden" name="referer" value="list" /> >- <input type="hidden" name="public" value="[% public | html %]" /> >- <input type="hidden" name="shelfnumber" value="[% shelf.shelfnumber | html %]" /> >- <button type="submit" class="btn btn-link remove remove_share" data-shelfname="[% shelf.shelfname | html %]" data-shelfnumber="[% shelf.shelfnumber | html %]"> >- <i class="fa fa-times" aria-hidden="true"></i> Remove share >- </button> >- </form> >- [% END # /IF can_manage_shelf %] >+ <input type="submit" class="btn btn-primary btn-sm" id="sort-submit" value="Resort list" /> >+ </form> >+ <!-- /#sorting-form --> > </div> >- <!-- /.list-actions --> >- >- <form method="get" action="/cgi-bin/koha/opac-shelves.pl" id="sorting-form" class="d-inline sort_by"> >- <input type="hidden" name="op" value="view" /> >- <input type="hidden" name="shelfnumber" value="[% shelf.shelfnumber | html %]" /> >- >- <label for="sortfield">Sort by: </label> >- <select name="sortfield" id="sortfield" class="resort"> >- <option value="">Default sorting</option> >- <optgroup label="Author"> >- [% IF sortfield == 'author' && direction == 'asc' %] >- <option value="author" data-direction="asc" selected="selected">Author (A-Z)</option> >- [% ELSE %] >- <option value="author" data-direction="asc">Author (A-Z)</option> >- [% END %] >- [% IF sortfield == 'author' && direction == 'desc' %] >- <option value="author" data-direction="desc" selected="selected">Author (Z-A)</option> >- [% ELSE %] >- <option value="author" data-direction="desc">Author (Z-A)</option> >- [% END %] >- </optgroup> >- <optgroup label="Title"> >- [% IF sortfield == 'title' && direction == 'asc' %] >- <option value="title" data-direction="asc" selected="selected">Title (A-Z)</option> >- [% ELSE %] >- <option value="title" data-direction="asc">Title (A-Z)</option> >- [% END %] >- [% IF sortfield == 'title' && direction == 'desc' %] >- <option value="title" data-direction="desc" selected="selected">Title (Z-A)</option> >- [% ELSE %] >- <option value="title" data-direction="desc">Title (Z-A)</option> >- [% END %] >- </optgroup> >- <optgroup label="Call number"> >- [% IF sortfield == 'itemcallnumber' && direction == 'asc' %] >- <option value="itemcallnumber" data-direction="asc" selected="selected">Call number (A-Z)</option> >- [% ELSE %] >- <option value="itemcallnumber" data-direction="asc">Call number (A-Z)</option> >- [% END %] >- [% IF sortfield == 'itemcallnumber' && direction == 'desc' %] >- <option value="itemcallnumber" data-direction="desc" selected="selected">Call number (Z-A)</option> >- [% ELSE %] >- <option value="itemcallnumber" data-direction="desc">Call number (Z-A)</option> >- [% END %] >- </optgroup> >- <optgroup label="Copyright date"> >- [% IF sortfield == 'copyrightdate' && direction == 'asc' %] >- <option value="copyrightdate" data-direction="desc" selected="selected">Copyright date (newest to oldest)</option> >- [% ELSE %] >- <option value="copyrightdate" data-direction="desc">Copyright date (newest to oldest)</option> >- [% END %] >- [% IF sortfield == 'copyrightdate' && direction == 'desc' %] >- <option value="copyrightdate" data-direction="asc" selected="selected">Copyright date (oldest to newest)</option> >- [% ELSE %] >- <option value="copyrightdate" data-direction="asc">Copyright date (oldest to newest)</option> >- [% END %] >- </optgroup> >- <optgroup label="Date added"> >- [% IF sortfield == "dateadded" && direction == 'asc' %] >- <option value="dateadded" data-direction="desc" selected="selected">Date added (newest to oldest)</option> >- [% ELSE %] >- <option value="dateadded" data-direction="desc">Date added (newest to oldest)</option> >- [% END %] >- [% IF sortfield == "dateadded" && direction == 'desc' %] >- <option value="dateadded" data-direction="asc" selected="selected">Date added (oldest to newest)</option> >- [% ELSE %] >- <option value="dateadded" data-direction="asc">Date added (oldest to newest)</option> >- [% END %] >- </optgroup> >- </select> >- >- <input type="submit" class="btn btn-primary btn-sm" id="sort-submit" value="Resort list" /> >- </form> >- <!-- /#sorting-form --> >+ <!-- / #toolbar --> >+ [% INCLUDE 'result-batch-controls.inc' %] > </div> >- <!-- / #toolbar --> >- [% INCLUDE 'result-batch-controls.inc' %] >+ <!-- / .toolbar-collapse --> > </div> > <!-- /#floating --> > >@@ -1094,6 +1100,16 @@ > Dopop( link ); > }); > >+ $(".toolbar-collapse-toggle").on("click", function(e) { >+ e.preventDefault(); >+ var $toggle = $(this); >+ var $collapse = $toggle.siblings(".toolbar-collapse"); >+ var isOpen = $collapse.hasClass("open"); >+ $collapse.toggleClass("open"); >+ $toggle.attr("aria-expanded", !isOpen ? "true" : "false"); >+ $toggle.find("i").toggleClass("fa-bars fa-times"); >+ }); >+ > }); // document.ready > > function sortMenu( sorting_form ){ >-- >2.53.0
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Diff
|
Splinter Review
Attachments on
bug 41963
:
194340
|
194353
| 194355 |
194356
|
194359