Skip to content

Commit e7faff5

Browse files
committed
Fixed #36529 -- Improved admin changelist filter UX on mobile screen.
1 parent 94c2f3b commit e7faff5

File tree

7 files changed

+138
-17
lines changed

7 files changed

+138
-17
lines changed

django/contrib/admin/static/admin/css/base.css

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,9 @@ html[data-theme="light"],
6363
--object-tools-bg: var(--close-button-bg);
6464
--object-tools-hover-bg: var(--close-button-hover-bg);
6565

66+
--sidebar-toggle-bg: #417690;
67+
--sidebar-toggle-pressed-bg: #264b5d;
68+
6669
--font-family-primary:
6770
"Segoe UI",
6871
system-ui,
@@ -827,7 +830,7 @@ a.deletelink:focus, a.deletelink:hover {
827830
border-radius: 15px;
828831
}
829832

830-
.object-tools a:link, .object-tools a:visited {
833+
.object-tools a:link, .object-tools a:visited, .sidebar-trigger button {
831834
display: block;
832835
float: left;
833836
padding: 3px 12px;

django/contrib/admin/static/admin/css/changelists.css

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,51 @@
149149
word-break: break-word;
150150
}
151151

152+
.changelist-tools {
153+
display: flex;
154+
justify-content: space-between;
155+
}
156+
157+
/* SIDEBAR TRIGGER */
158+
159+
.sidebar-trigger {
160+
display: none;
161+
margin-top: 0;
162+
padding: 0;
163+
}
164+
165+
.sidebar-trigger li {
166+
list-style-type: none;
167+
}
168+
169+
.sidebar-trigger button {
170+
display: flex;
171+
gap: 8px;
172+
outline: none;
173+
box-sizing: content-box;
174+
border: none;
175+
line-height: 20px;
176+
cursor: pointer;
177+
background-color: var(--sidebar-toggle-bg);
178+
border-radius: 15px;
179+
}
180+
181+
.sidebar-trigger button::after {
182+
content: '';
183+
display: inline-block;
184+
background: url("../img/icon-sidebar-toggle-open.svg") 0 1px no-repeat;
185+
height: 16px;
186+
width: 16px;
187+
}
188+
189+
.sidebar-trigger button[aria-pressed="true"] {
190+
background-color: var(--sidebar-toggle-pressed-bg);
191+
}
192+
193+
.sidebar-trigger button[aria-pressed="true"]::after {
194+
background: url("../img/icon-sidebar-toggle-close.svg") 0 1px no-repeat;
195+
}
196+
152197
/* FILTER COLUMN */
153198

154199
#changelist-filter {

django/contrib/admin/static/admin/css/responsive.css

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,10 @@ input[type="submit"], button {
383383
padding: 15px;
384384
}
385385

386+
#content:has(.sidebar-trigger button.filter[aria-pressed="true"]) {
387+
margin-bottom: 40vh;
388+
}
389+
386390
div.breadcrumbs {
387391
padding: 10px 15px;
388392
}
@@ -436,15 +440,29 @@ input[type="submit"], button {
436440
}
437441

438442
#changelist-filter {
439-
position: static;
440-
width: auto;
441-
margin-top: 30px;
443+
display: none;
444+
position: fixed;
445+
bottom: 0;
446+
left: 0;
447+
height: 40vh;
448+
width: 100%;
449+
overflow: scroll;
450+
}
451+
452+
#changelist-filter.shifted {
453+
display: block;
442454
}
443455

444456
.object-tools {
445457
text-align: left;
446458
}
447459

460+
/* SIDEBAR TRIGGER */
461+
462+
.sidebar-trigger {
463+
display: block;
464+
}
465+
448466
/* Forms */
449467

450468
.form-row {
Lines changed: 4 additions & 0 deletions
Loading
Lines changed: 4 additions & 0 deletions
Loading

django/contrib/admin/static/admin/js/nav_sidebar.js

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,46 @@
2323
});
2424
}
2525

26+
const mediaQuery = window.matchMedia('(max-width: 767px)');
27+
let filterSidebarToggleRegistered = false;
28+
function handleViewportChange(e) {
29+
const toggleFilterSidebar = document.getElementById('toggle-filter-sidebar');
30+
const filterSidebar = document.getElementById('changelist-filter');
31+
if (e.matches) {
32+
// Mobile
33+
if (toggleFilterSidebar !== null) {
34+
let filterSidebarIsOpen = localStorage.getItem('django.admin.filterSidebarIsOpen');
35+
if (filterSidebarIsOpen === null) {
36+
filterSidebarIsOpen = 'true';
37+
}
38+
localStorage.setItem('django.admin.filterSidebarIsOpen', filterSidebarIsOpen);
39+
filterSidebar.classList.toggle('shifted', filterSidebarIsOpen === 'true');
40+
filterSidebar.setAttribute('aria-expanded', filterSidebarIsOpen);
41+
toggleFilterSidebar.setAttribute('aria-pressed', filterSidebarIsOpen);
42+
43+
if (!filterSidebarToggleRegistered) {
44+
toggleFilterSidebar.addEventListener('click', function() {
45+
if (filterSidebarIsOpen === 'true') {
46+
filterSidebarIsOpen = 'false';
47+
} else {
48+
filterSidebarIsOpen = 'true';
49+
}
50+
localStorage.setItem('django.admin.filterSidebarIsOpen', filterSidebarIsOpen);
51+
filterSidebar.classList.toggle('shifted');
52+
filterSidebar.setAttribute('aria-expanded', filterSidebarIsOpen);
53+
toggleFilterSidebar.setAttribute('aria-pressed', filterSidebarIsOpen);
54+
});
55+
filterSidebarToggleRegistered = true;
56+
}
57+
}
58+
} else {
59+
// Tablet, Desktop
60+
filterSidebar.removeAttribute('aria-expanded');
61+
}
62+
}
63+
handleViewportChange(mediaQuery);
64+
mediaQuery.addEventListener('change', handleViewportChange);
65+
2666
function initSidebarQuickFilter() {
2767
const options = [];
2868
const navSidebar = document.getElementById('nav-sidebar');

django/contrib/admin/templates/admin/change_list.html

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -41,19 +41,26 @@
4141

4242
{% block content %}
4343
<div id="content-main">
44-
{% block object-tools %}
45-
<ul class="object-tools">
46-
{% block object-tools-items %}
47-
{% change_list_object_tools %}
48-
{% endblock %}
49-
</ul>
50-
{% endblock %}
51-
{% if cl.formset and cl.formset.errors %}
52-
<p class="errornote">
53-
{% blocktranslate count counter=cl.formset.total_error_count %}Please correct the error below.{% plural %}Please correct the errors below.{% endblocktranslate %}
54-
</p>
55-
{{ cl.formset.non_form_errors }}
56-
{% endif %}
44+
<div class="changelist-tools">
45+
{% block sidebar-trigger %}
46+
<ul class="sidebar-trigger">
47+
{% if cl.has_filters %}<li><button id="toggle-filter-sidebar" class="filter">Filter</button></li>{% endif %}
48+
</ul>
49+
{% endblock %}
50+
{% block object-tools %}
51+
<ul class="object-tools">
52+
{% block object-tools-items %}
53+
{% change_list_object_tools %}
54+
{% endblock %}
55+
</ul>
56+
{% endblock %}
57+
</div>
58+
{% if cl.formset and cl.formset.errors %}
59+
<p class="errornote">
60+
{% blocktranslate count counter=cl.formset.total_error_count %}Please correct the error below.{% plural %}Please correct the errors below.{% endblocktranslate %}
61+
</p>
62+
{{ cl.formset.non_form_errors }}
63+
{% endif %}
5764
<div class="module{% if cl.has_filters %} filtered{% endif %}" id="changelist">
5865
<div class="changelist-form-container">
5966
{% block search %}{% search_form cl %}{% endblock %}

0 commit comments

Comments
 (0)