Skip to content

Commit 5e494e2

Browse files
authored
feat: support for nested tables in table component (#1496)
1 parent 734d224 commit 5e494e2

File tree

4 files changed

+85
-10
lines changed

4 files changed

+85
-10
lines changed

docs/components/table.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,28 @@ def dashboard_callback(request):
3838
{% endcomponent %}
3939
```
4040

41+
## Nested tables data structure
42+
43+
```python
44+
data = {
45+
"headers": ["col1", "col 2"],
46+
"rows": [
47+
# Classic row
48+
["a", "b"],
49+
# Row with nested table
50+
{
51+
"cols": ["c", "d"], # Cols in row
52+
"table": {
53+
"headers": ["col2", "col3"]
54+
"rows" [
55+
["g", "h"]
56+
]
57+
}
58+
}
59+
]
60+
}
61+
```
62+
4163
## Table component parameters
4264

4365
| Parameter | Description |

src/unfold/static/unfold/css/styles.css

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/unfold/templates/unfold/components/table.html

Lines changed: 55 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,14 @@ <h3 class="font-semibold mb-1 text-font-important-light text-sm dark:text-font-i
1111
<div {% if height %}style="max-height: {{ height }}px;" data-simplebar{% endif %}>
1212
<table class="block border-spacing-none border-separate w-full lg:table">
1313
{% if table.headers %}
14-
<thead class="text-font-important-light dark:text-font-important-dark {% if height %}sticky top-0{% endif %}">
14+
<thead class="text-font-important-light dark:text-font-important-dark {% if height %}sticky top-0 z-100{% endif %}">
1515
<tr class="bg-base-50 dark:bg-base-900">
16-
{% for header in table.headers %}
17-
<th class="align-middle border-b border-base-200 font-semibold py-2 text-left whitespace-nowrap sortable column-description hidden px-3 lg:table-cell dark:border-base-800 dark:bg-white/[.02] {% if card_included == 1 %}first:pl-6 last:pr-6{% endif %}">
16+
{% if table|has_nested_tables %}
17+
<th class="align-middle border-b border-base-200 font-semibold py-2 text-left whitespace-nowrap hidden px-3 lg:table-cell dark:border-base-800 dark:bg-white/[.02]"></th>
18+
{% endif %}
19+
20+
{% for header in table.headers %}
21+
<th class="align-middle border-b border-base-200 font-semibold py-2 text-left whitespace-nowrap hidden px-3 lg:table-cell dark:border-base-800 dark:bg-white/[.02] {% if card_included == 1 %}first:pl-6 last:pr-6{% endif %}">
1822
{{ header|capfirst }}
1923
</th>
2024
{% endfor %}
@@ -23,17 +27,59 @@ <h3 class="font-semibold mb-1 text-font-important-light text-sm dark:text-font-i
2327
{% endif %}
2428

2529
{% if table.rows %}
26-
<tbody class="block lg:table-row-group">
27-
{% for row in table.rows %}
28-
<tr class="{% if striped == 1 %}{% cycle '' 'bg-base-50 dark:bg-white/[.02]' %}{% endif %} block group {% if forloop.first %}first-row{% endif %} {% if not card_included == 1 %}border border-base-200 mb-3 rounded-default shadow-xs{% else %}border-b border-base-200 last:border-b-0{% endif %} lg:table-row lg:border-none lg:mb-0 lg:shadow-none dark:border-base-800">
29-
{% for cell in row %}
30+
{% for row in table.rows %}
31+
<tbody class="block relative lg:table-row-group" x-data="{ rowOpen: false }">
32+
<tr class="{% if striped == 1 %}{% cycle '' 'bg-base-50 dark:bg-white/[.02]' %}{% endif %} block group {% if forloop.first %}first-row{% endif %} {% if not card_included == 1 %}border border-base-200 mb-3 rounded-default shadow-xs{% else %}border-b border-base-200 {% if forloop.last %}last:border-b-0{% endif %}{% endif %} lg:table-row lg:border-none lg:mb-0 lg:shadow-none dark:border-base-800">
33+
{% if row.table.rows %}
34+
<td class="cursor-pointer px-3 py-2 lg:pr-0 align-middle flex border-t border-base-200 font-normal gap-4 min-w-0 overflow-hidden text-left before:flex before:capitalize before:content-[attr(data-label)] before:font-semibold before:text-font-important-light dark:before:text-font-important-dark before:items-center before:mr-auto max-lg:first:border-t-0 lg:group-[.first-row]:border-t-0 lg:before:hidden lg:py-3 lg:table-cell dark:border-base-800 lg:w-px" data-label="{% trans "Expand row" %}">
35+
<span class="material-symbols-outlined select-none block! h-[18px] w-[18px] -rotate-90 transition-all" x-on:click="rowOpen = !rowOpen" x-bind:class="{'rotate-0': rowOpen}">
36+
expand_more
37+
</span>
38+
</td>
39+
{% endif %}
40+
41+
{% for cell in row.cols|default:row %}
3042
<td class="px-3 py-2 align-middle flex border-t border-base-200 font-normal gap-4 min-w-0 overflow-hidden text-left before:flex before:capitalize before:content-[attr(data-label)] before:font-semibold before:text-font-important-light dark:before:text-font-important-dark before:items-center before:mr-auto first:border-t-0 lg:group-[.first-row]:border-t-0 lg:before:hidden {% if not forloop.parentloop.first %}lg:first:border-t{% endif %} lg:py-3 lg:table-cell dark:border-base-800 {% if card_included == 1 %}lg:first:pl-6 lg:last:pr-6{% endif %}" {% if table.headers %}data-label="{{ table.headers|index:forloop.counter0 }}"{% endif %}>
3143
{{ cell }}
3244
</td>
3345
{% endfor %}
3446
</tr>
35-
{% endfor %}
36-
</tbody>
47+
48+
{% if "table" in row %}
49+
<tr class="block lg:table-row" x-show="rowOpen">
50+
<td class="block lg:table-cell" colspan="100%">
51+
<div class="absolute bg-primary-600 -bottom-px hidden left-0 top-0 w-0.5 z-[50] lg:block" x-show="rowOpen"></div>
52+
53+
<table class="block w-full lg:table">
54+
{% if row.table.headers %}
55+
<thead>
56+
<tr class="bg-base-50 dark:bg-white/[.02]">
57+
{% for header in row.table.headers %}
58+
<th class="align-middle border-b border-t border-base-200 font-semibold py-2 text-left whitespace-nowrap sortable column-description hidden px-3 lg:table-cell dark:border-base-800 dark:bg-white/[.02]">
59+
{{ header }}
60+
</th>
61+
{% endfor %}
62+
</tr>
63+
</thead>
64+
{% endif %}
65+
66+
<tbody class="block lg:table-row-group">
67+
{% for nested_row in row.table.rows %}
68+
<tr class="{% if row.table.striped == 1 %}{% cycle '' 'bg-base-50 dark:bg-white/[.02]' %}{% endif %} block group {% if forloop.first %}first-row{% endif %} border border-base-200 mb-3 rounded-default shadow-xs lg:table-row lg:border-none lg:mb-0 lg:shadow-none dark:border-base-800">
69+
{% for cell in nested_row %}
70+
<td class="px-3 py-2 align-middle flex border-t border-base-200 font-normal gap-4 min-w-0 overflow-hidden text-left before:flex before:capitalize before:content-[attr(data-label)] before:font-semibold before:text-font-important-light dark:before:text-font-important-dark before:items-center before:mr-auto first:border-t-0 lg:group-[.first-row]:border-t-0 lg:before:hidden {% if not forloop.parentloop.first %}lg:first:border-t{% endif %} lg:py-3 lg:table-cell dark:border-base-800" {% if row.table.headers %}data-label="{{ row.table.headers|index:forloop.counter0 }}"{% endif %}>
71+
{{ cell }}
72+
</td>
73+
{% endfor %}
74+
</tr>
75+
{% endfor %}
76+
</tbody>
77+
</table>
78+
</td>
79+
</tr>
80+
{% endif %}
81+
</tbody>
82+
{% endfor %}
3783
{% endif %}
3884
</table>
3985
</div>

src/unfold/templatetags/unfold.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -737,3 +737,10 @@ def header_title(context: RequestContext) -> str:
737737
@register.filter
738738
def admin_object_app_url(object: Model, arg: str) -> str:
739739
return f"admin:{object._meta.app_label}_{object._meta.model_name}_{arg}"
740+
741+
742+
@register.filter
743+
def has_nested_tables(table: dict) -> bool:
744+
return any(
745+
isinstance(row, dict) and "table" in row for row in table.get("rows", [])
746+
)

0 commit comments

Comments
 (0)