Repository: rclement/datasette-dashboards (language: python)

Issue:
Improve filters layout
The current layout for filters is a simple HTML `form` behind a `summary` field:

<img width="441" alt="Screenshot 2023-03-21 at 11 05 57" src="https://user-images.githubusercontent.com/1238873/226575439-0e660290-3793-45df-97e1-31b038d7865f.png">

Instead, presenting the filters in a more useful way would be to use an HTML `fieldset` like this:

<img width="550" alt="Screenshot 2023-03-21 at 11 10 18" src="https://user-images.githubusercontent.com/1238873/226575432-ff210df9-2a96-4789-9bad-972b4fe2b56d.png">




Discussion / hints:

Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6931d15..073c912 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 - Table chart component
 - Map chart component
 
+### Changed
+- Improve filters layout
+
 ## [0.3.0] - 2023-03-14
 ### Added
 - Select filter type with static and dynamic values
diff --git a/datasette_dashboards/static/dashboards.css b/datasette_dashboards/static/dashboards.css
index bc5789e..4196f3c 100644
--- a/datasette_dashboards/static/dashboards.css
+++ b/datasette_dashboards/static/dashboards.css
@@ -2,6 +2,24 @@
   margin-bottom: 1rem;
 }
 
+.dashboard-filters-boxes {
+  display: flex;
+  flex-direction: row;
+  flex-wrap: wrap;
+}
+
+.dashboard-filters-boxes > fieldset {
+  margin: 0rem 1rem 0.5rem 0rem;
+  padding: 0rem 1rem 0.5rem 1rem;
+  border: 1px solid #a0a0a0;
+  border-radius: 4px;
+}
+
+.dashboard-filters-boxes > fieldset > legend {
+  padding: 0 0.2rem 0 0.2rem;
+  font-size: small;
+}
+
 .dashboard-grid {
   display: grid;
   grid-auto-columns: minmax(100px, 1fr);
diff --git a/datasette_dashboards/templates/dashboard_view.html b/datasette_dashboards/templates/dashboard_view.html
index 5d0b54b..3c87312 100644
--- a/datasette_dashboards/templates/dashboard_view.html
+++ b/datasette_dashboards/templates/dashboard_view.html
@@ -45,29 +45,29 @@ <h1>{{ dashboard.title }}</h1>
     </div>
 
     {% if dashboard.filters %}
-    <details class="dashboard-filters">
-      <summary>Filters</summary>
-
+    <div class="dashboard-filters">
       <form method="GET">
-        {% for key, dfilter in dashboard.filters.items() %}
-        {% set dfilter_type = dfilter.type if dfilter.type in ['text', 'date', 'number', 'select'] else 'text' %}
-        <p>
-          <label for="{{ key }}">{{ dfilter.name }}</label>
-          {% if dfilter_type == 'select' %}
-          <select id="{{ key }}" name="{{ key }}">
-            <option value="" {% if (key in query_parameters.keys() and query_parameters[key] == '') or  (key not in query_parameters.keys() and not dfilter.default) %}selected{% endif %}></option>
-            {% for option in dfilter.options %}
-            <option value="{{ option }}" {% if (key in query_parameters.keys() and query_parameters[key] == option) or (key not in query_parameters.keys() and dfilter.default == option) %}selected{% endif %}>{{ option }}</option>
-            {% endfor %}
-          </select>
-          {% else %}
-          <input id="{{ key }}" name="{{ key }}" type="{{ dfilter_type }}"{% if dfilter.min is defined %} min="{{ dfilter.min }}"{% endif %}{% if dfilter.max is defined %} max="{{ dfilter.max }}"{% endif %}{% if dfilter.step is defined %} step="{{ dfilter.step }}"{% endif %} value="{% if key in query_parameters.keys() %}{{ query_parameters[key] }}{% else %}{{ dfilter.default }}{% endif %}">
-          {% endif %}
-        </p>
-        {% endfor %}
+        <div class="dashboard-filters-boxes">
+          {% for key, dfilter in dashboard.filters.items() %}
+          {% set dfilter_type = dfilter.type if dfilter.type in ['text', 'date', 'number', 'select'] else 'text' %}
+          <fieldset>
+            <legend>{{ dfilter.name }}</legend>
+            {% if dfilter_type == 'select' %}
+            <select id="{{ key }}" name="{{ key }}">
+              <option value="" {% if (key in query_parameters.keys() and query_parameters[key] == '') or  (key not in query_parameters.keys() and not dfilter.default) %}selected{% endif %}></option>
+              {% for option in dfilter.options %}
+              <option value="{{ option }}" {% if (key in query_parameters.keys() and query_parameters[key] == option) or (key not in query_parameters.keys() and dfilter.default == option) %}selected{% endif %}>{{ option }}</option>
+              {% endfor %}
+            </select>
+            {% else %}
+            <input id="{{ key }}" name="{{ key }}" type="{{ dfilter_type }}"{% if dfilter.min is defined %} min="{{ dfilter.min }}"{% endif %}{% if dfilter.max is defined %} max="{{ dfilter.max }}"{% endif %}{% if dfilter.step is defined %} step="{{ dfilter.step }}"{% endif %} value="{% if key in query_parameters.keys() %}{{ query_parameters[key] }}{% else %}{{ dfilter.default }}{% endif %}">
+            {% endif %}
+          </fieldset>
+          {% endfor %}
+        </div>
         <input type="submit" value="Apply">
       </form>
-    </details>
+    </div>
     {% endif %}
 
     <div class="dashboard-grid">


Test patch:
diff --git a/tests/test_dashboard_view.py b/tests/test_dashboard_view.py
index e3892b3..c80951e 100644
--- a/tests/test_dashboard_view.py
+++ b/tests/test_dashboard_view.py
@@ -18,14 +18,14 @@ async def test_dashboard_views(datasette):
         assert "grid-template-areas" not in response.text
         assert "grid-area:" not in response.text
 
-        assert '<details class="dashboard-filters">' in response.text
+        assert '<div class="dashboard-filters">' in response.text
         for key, flt in dashboard["filters"].items():
             expected_type = (
                 flt["type"]
                 if flt["type"] in ["text", "date", "number", "select"]
                 else "text"
             )
-            assert f'<label for="{key}">{flt["name"]}</label>' in response.text
+            assert f'<legend>{flt["name"]}</legend>' in response.text
             if flt["type"] == "select":
                 assert f'<select id="{key}" name="{key}">' in response.text
                 assert '<option value="" selected></option>' in response.text
@@ -98,8 +98,8 @@ async def test_dashboard_view_parameters(datasette):
     )
     assert response.status_code == 200
 
-    assert '<details class="dashboard-filters">' in response.text
-    assert '<label for="date_start">Date Start</label>' in response.text
+    assert '<div class="dashboard-filters">' in response.text
+    assert "<legend>Date Start</legend>" in response.text
     assert (
         '<input id="date_start" name="date_start" type="date" value="2021-01-01">'
         in response.text
@@ -120,8 +120,8 @@ async def test_dashboard_view_parameters_empty(datasette):
     response = await datasette.client.get("/-/dashboards/job-dashboard?date_start=")
     assert response.status_code == 200
 
-    assert '<details class="dashboard-filters">' in response.text
-    assert '<label for="date_start">Date Start</label>' in response.text
+    assert '<div class="dashboard-filters">' in response.text
+    assert "<legend>Date Start</legend>" in response.text
     assert (
         '<input id="date_start" name="date_start" type="date" value="">'
         in response.text