django-unfold 0.49.1__py3-none-any.whl → 0.51.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. django_unfold-0.51.0.dist-info/METADATA +81 -0
  2. {django_unfold-0.49.1.dist-info → django_unfold-0.51.0.dist-info}/RECORD +34 -30
  3. {django_unfold-0.49.1.dist-info → django_unfold-0.51.0.dist-info}/WHEEL +1 -1
  4. unfold/admin.py +4 -0
  5. unfold/contrib/filters/admin/dropdown_filters.py +1 -0
  6. unfold/contrib/filters/admin/mixins.py +10 -4
  7. unfold/contrib/filters/forms.py +2 -2
  8. unfold/dataclasses.py +3 -0
  9. unfold/decorators.py +8 -0
  10. unfold/enums.py +10 -0
  11. unfold/mixins/action_model_admin.py +4 -0
  12. unfold/sections.py +82 -0
  13. unfold/settings.py +1 -0
  14. unfold/sites.py +3 -0
  15. unfold/static/admin/js/inlines.js +18 -5
  16. unfold/static/unfold/css/styles.css +1 -1
  17. unfold/static/unfold/js/select2.init.js +29 -0
  18. unfold/styles.css +1 -1
  19. unfold/templates/admin/change_list.html +16 -16
  20. unfold/templates/admin/change_list_results.html +38 -40
  21. unfold/templates/admin/filter.html +2 -2
  22. unfold/templates/unfold/components/card.html +1 -1
  23. unfold/templates/unfold/components/table.html +42 -26
  24. unfold/templates/unfold/helpers/change_list_filter_actions.html +1 -1
  25. unfold/templates/unfold/helpers/empty_results.html +35 -0
  26. unfold/templates/unfold/helpers/tab_action.html +24 -19
  27. unfold/templates/unfold/helpers/tab_actions.html +19 -0
  28. unfold/templates/unfold/helpers/tab_items.html +47 -0
  29. unfold/templates/unfold/helpers/tab_list.html +3 -66
  30. unfold/templates/unfold/layouts/skeleton.html +1 -2
  31. unfold/templatetags/unfold.py +81 -2
  32. unfold/templatetags/unfold_list.py +10 -10
  33. unfold/views.py +14 -1
  34. django_unfold-0.49.1.dist-info/METADATA +0 -75
  35. unfold/contrib/filters/admin.py +0 -619
  36. {django_unfold-0.49.1.dist-info → django_unfold-0.51.0.dist-info}/LICENSE.md +0 -0
@@ -6,10 +6,39 @@
6
6
  $.each(this, function (i, element) {
7
7
  $(element).select2();
8
8
  });
9
+
10
+ return this;
11
+ };
12
+
13
+ $.fn.djangoAdminSelect2 = function () {
14
+ $.each(this, function (i, element) {
15
+ $(element).select2({
16
+ ajax: {
17
+ data: (params) => {
18
+ return {
19
+ term: params.term,
20
+ page: params.page,
21
+ app_label: element.dataset.appLabel,
22
+ model_name: element.dataset.modelName,
23
+ field_name: element.dataset.fieldName,
24
+ };
25
+ },
26
+ },
27
+ });
28
+ });
9
29
  return this;
10
30
  };
11
31
 
12
32
  $(function () {
13
33
  $(".unfold-admin-autocomplete.admin-autocomplete").djangoCustomSelect2();
34
+
35
+ $(".admin-autocomplete")
36
+ .not(".unfold-admin-autocomplete")
37
+ .not("[name*=__prefix__]")
38
+ .djangoAdminSelect2();
39
+ });
40
+
41
+ document.addEventListener("formset:added", (event) => {
42
+ $(event.target).find(".admin-autocomplete").djangoAdminSelect2();
14
43
  });
15
44
  }
unfold/styles.css CHANGED
@@ -181,7 +181,7 @@ table tr.selected th {
181
181
  Selector
182
182
  *******************************************************/
183
183
  .selector {
184
- @apply flex flex-col flex-grow items-center max-w-2xl md:flex-row;
184
+ @apply flex flex-col flex-grow items-center md:flex-row;
185
185
  }
186
186
 
187
187
  .selector select {
@@ -61,22 +61,6 @@
61
61
  {{ cl.formset.non_form_errors }}
62
62
  {% endif %}
63
63
 
64
- <div class="flex flex-col gap-4 mb-4 sm:flex-row">
65
- {% block search %}
66
- {% search_form cl %}
67
- {% endblock %}
68
-
69
- {% block filters %}
70
- {% if cl.has_filters %}
71
- <a class="{% if cl.has_active_filters %}bg-primary-600 border-primary-600 text-white{% else %}bg-white border-base-200 dark:bg-base-900 dark:border-base-700{% endif %} border cursor-pointer flex font-medium group items-center px-3 py-2 rounded shadow-sm text-sm lg:ml-auto md:mt-0 {% if not cl.model_admin.list_filter_sheet %}2xl:hidden{% endif %}" x-on:click="filterOpen = true" x-on:keydown.escape.window="filterOpen = false">
72
- {% trans "Filters" %}
73
-
74
- <span class="material-symbols-outlined md-18 ml-auto -mr-1 pl-4 {% if cl.has_active_filters %}text-white{% else %}text-base-400 group-hover:text-base-500 dark:group-hover:text-base-400 dark:text-base-500{% endif %}">filter_list</span>
75
- </a>
76
- {% endif %}
77
- {% endblock %}
78
- </div>
79
-
80
64
  <div class="flex -mx-4 module{% if cl.has_filters %} filtered{% endif %}" id="changelist" x-data="{ changeListWidth: 0 }">
81
65
  <div class="changelist-form-container flex flex-row flex-grow gap-6 min-w-0 px-4">
82
66
  <div class="flex-grow min-w-0 lg:pb-16" x-resize="changeListWidth = $width">
@@ -90,6 +74,22 @@
90
74
  {% include cl.model_admin.list_before_template %}
91
75
  {% endif %}
92
76
 
77
+ <div class="flex flex-col gap-4 mb-4 sm:flex-row empty:hidden lg:border lg:border-base-200 lg:dark:border-base-800 lg:-mb-8 lg:p-3 lg:pb-11 lg:rounded-t">
78
+ {% block search %}
79
+ {% search_form cl %}
80
+ {% endblock %}
81
+
82
+ {% block filters %}
83
+ {% if cl.has_filters %}
84
+ <a class="{% if cl.has_active_filters %}bg-primary-600 border-primary-600 text-white{% else %}bg-white border-base-200 hover:text-primary-600 dark:bg-base-900 dark:border-base-700 dark:hover:text-primary-500{% endif %} border cursor-pointer flex font-medium gap-2 group items-center px-3 py-2 rounded shadow-sm text-sm lg:ml-auto md:mt-0 {% if not cl.model_admin.list_filter_sheet %}2xl:hidden{% endif %}" x-on:click="filterOpen = true" x-on:keydown.escape.window="filterOpen = false">
85
+ {% trans "Filters" %}
86
+
87
+ <span class="material-symbols-outlined md-18 ml-auto">filter_list</span>
88
+ </a>
89
+ {% endif %}
90
+ {% endblock %}
91
+ </div>
92
+
93
93
  <form id="changelist-form" class="group" method="post"{% if cl.formset and cl.formset.is_multipart %} enctype="multipart/form-data"{% endif %} novalidate>
94
94
  {% csrf_token %}
95
95
 
@@ -1,4 +1,4 @@
1
- {% load admin_urls i18n %}
1
+ {% load admin_urls i18n unfold %}
2
2
 
3
3
  {% if result_hidden_fields %}
4
4
  <div class="hiddenfields">
@@ -7,10 +7,14 @@
7
7
  {% endif %}
8
8
 
9
9
  {% if results %}
10
- <div class="-mx-1 px-1 overflow-x-auto lg:border lg:border-base-200 lg:mx-0 lg:px-0 lg:rounded lg:shadow-sm lg:dark:border-base-800 {% if cl.model_admin.list_horizontal_scrollbar_top %}simplebar-horizontal-scrollbar-top{% endif %}" data-simplebar data-simplebar-auto-hide="false">
10
+ <div class="-mx-1 px-1 overflow-x-auto lg:border lg:border-base-200 lg:mx-0 lg:px-0 lg:rounded lg:shadow-sm lg:dark:border-base-800 lg:bg-white lg:dark:bg-base-900 {% if cl.model_admin.list_horizontal_scrollbar_top %}simplebar-horizontal-scrollbar-top{% endif %}" data-simplebar data-simplebar-auto-hide="false">
11
11
  <table id="result_list" class="block border-base-200 border-spacing-none border-separate w-full lg:table">
12
12
  <thead>
13
13
  <tr>
14
+ {% if cl.model_admin.list_sections|length > 0 %}
15
+ <th></th>
16
+ {% endif %}
17
+
14
18
  {% for header in result_headers %}
15
19
  <th class="align-middle font-semibold py-2 text-left text-font-important-light dark:text-font-important-dark whitespace-nowrap {{ header.class_attrib }} {% if "action-toggle" in header.text and forloop.counter == 1 %}lg:px-3 lg:w-10{% else %}hidden px-3 lg:table-cell{% endif %}" scope="col">
16
20
  <div class="flex items-center">
@@ -65,8 +69,8 @@
65
69
  </tr>
66
70
  </thead>
67
71
 
68
- <tbody class="block lg:table-row-group">
69
- {% for result in results %}
72
+ {% for result in results %}
73
+ <tbody class="block lg:table-row-group" x-data="{rowOpen: false}">
70
74
  {% if result.form and result.form.non_field_errors %}
71
75
  <tr>
72
76
  <td class="text-left" colspan="{{ result|length }}">
@@ -76,47 +80,41 @@
76
80
  {% endif %}
77
81
 
78
82
  <tr class="{% cycle '' 'bg-base-50 dark:bg-white/[.02]' %} block border mb-3 rounded shadow-sm lg:table-row lg:border-none lg:mb-0 lg:shadow-none dark:border-base-800">
83
+ {% if cl.model_admin.list_sections|length > 0 %}
84
+ <td class="align-middle cursor-pointer flex border-b border-base-200 font-normal px-2.5 py-2 relative text-left before:font-semibold before:text-font-important-light before:block before:capitalize before:content-[attr(data-label)] before:mr-auto lg:before:hidden lg:border-b-0 lg:border-t lg:pl-3 lg:pr-0 lg:py-3 lg:table-cell dark:border-base-800 dark:lg:border-base-800 dark:before:text-font-important-dark lg:w-px"
85
+ data-label="{% trans "Expand row" %}"
86
+ x-on:click="rowOpen = !rowOpen">
87
+ <div class="absolute bg-primary-600 -bottom-px hidden left-0 top-0 w-0.5 z-10 lg:block" x-show="rowOpen"></div>
88
+ <span class="material-symbols-outlined select-none !block -rotate-90 transition-all" x-bind:class="rowOpen && 'rotate-0'">
89
+ expand_more
90
+ </span>
91
+ </td>
92
+ {% endif %}
93
+
79
94
  {% for item in result %}
80
95
  {{ item }}
81
96
  {% endfor %}
82
- {% include 'unfold/helpers/actions_row.html' with actions=actions_row instance_pk=result.instance_pk %}
83
- </tr>
84
- {% endfor %}
85
- </tbody>
86
- </table>
87
- </div>
88
- {% else %}
89
- {% url cl.opts|admin_urlname:"add" as add_url %}
90
- {% blocktranslate with name=cl.opts.verbose_name asvar title %}Add {{ name }}{% endblocktranslate %}
91
-
92
- <div class="border border-base-200 flex flex-col items-center px-8 py-24 rounded shadow-sm dark:border-base-800">
93
- <div class="bg-base-100 flex h-24 items-center justify-center mb-8 rounded-full w-24 dark:bg-base-800">
94
- <span class="material-symbols-outlined text-base-500 !text-5xl dark:text-base-400">inbox</span>
95
- </div>
96
97
 
97
- <h2 class="font-semibold mb-1 text-xl text-font-important-light dark:text-font-important-dark">
98
- {% trans "No results found" %}
99
- </h2>
100
-
101
- <p class="mb-6 text-center">
102
- {% trans "This page yielded into no results. Create a new item or reset your filters." %}
103
- </p>
104
-
105
- {% if has_add_permission or cl.has_filters %}
106
- <div class="flex flex-col gap-4 justify-center w-full lg:flex-row">
107
- {% if has_add_permission %}
108
- <a href="{% add_preserved_filters add_url is_popup to_field %}" class="bg-primary-600 flex flex-row font-medium gap-2 items-center h-9.5 justify-center px-3 py-2 rounded text-white w-full lg:w-auto">
109
- <span class="material-symbols-outlined text-white">add</span> {{ title }}
110
- </a>
111
- {% endif %}
98
+ {% include 'unfold/helpers/actions_row.html' with actions=actions_row instance_pk=result.instance.pk %}
99
+ </tr>
112
100
 
101
+ {% if cl.model_admin.list_sections|length > 0 %}
102
+ <tr class="block mb-3 lg:table-row" x-show="rowOpen">
103
+ <td colspan="{{ result|length|add:1 }}" class="border bg-base-200/10 block border-base-200 relative rounded p-3 lg:shadow-inner lg:border-0 lg:border-t lg:rounded-none lg:table-cell dark:border-base-800">
104
+ <div class="absolute bg-primary-600 h-full hidden left-0 top-0 w-0.5 lg:block"></div>
113
105
 
114
- {% if cl.has_filters %}
115
- <a href="{{ cl.clear_all_filters_qs }}" class="border border-base-200 flex flex-row font-medium gap-2 group/button h-9.5 items-center justify-center px-3 py-2 rounded transition-all w-full hover:text-primary-600 lg:w-auto dark:border-base-700 dark:hover:bg-base-900 dark:hover:text-primary-500">
116
- <span class="material-symbols-outlined group-hover/button:text-primary-600 dark:group-hover/button:text-base-500 text-base-400 dark:text-base-500">filter_list_off</span> {% trans "Reset filters" %}
117
- </a>
118
- {% endif %}
119
- </div>
120
- {% endif %}
106
+ <div class="grid gap-3 {{ cl.model_admin.list_sections_classes }}">
107
+ {% for section in cl.model_admin.list_sections %}
108
+ {% render_section cl.model_admin.list_sections|index:forloop.counter0 result.instance %}
109
+ {% endfor %}
110
+ </div>
111
+ </td>
112
+ </tr>
113
+ {% endif %}
114
+ </tbody>
115
+ {% endfor %}
116
+ </table>
121
117
  </div>
118
+ {% else %}
119
+ {% include 'unfold/helpers/empty_results.html' %}
122
120
  {% endif %}
@@ -11,11 +11,11 @@
11
11
  {% endif %}
12
12
  {% endfor %}
13
13
 
14
- {% if spec|class_name == "BooleanFieldListFilter" %}
14
+ {% if spec|class_name == "BooleanFieldListFilter" or spec.horizontal %}
15
15
  <ul class="dark:bg-base-900 border border-base-200 flex min-w-20 rounded shadow-sm text-font-default-light dark:border-base-700 dark:text-font-default-dark w-full">
16
16
  {% for choice in choices %}
17
17
  <li class="basis-1/3 border-r border-base-200 flex-grow truncate last:border-r-0 dark:border-base-700 {% if choice.selected %}font-semibold text-primary-600 dark:text-primary-500 {% else %}hover:text-base-700 dark:hover:text-base-200{% endif %}">
18
- <a href="{{ choice.query_string|iriencode }}" title="{{ choice.display }}" class="block px-3 py-2 text-center hover:text-primary-600 dark:hover:text-primary-500">
18
+ <a href="{{ choice.query_string|iriencode }}" title="{{ choice.display }}" class="block py-2 text-center hover:text-primary-600 dark:hover:text-primary-500">
19
19
  {{ choice.display }}
20
20
  </a>
21
21
  </li>
@@ -1,4 +1,4 @@
1
- <div class="border flex flex-col flex-grow overflow-hidden p-6 relative rounded shadow-sm dark:border-base-800 {% if class %} {{ class }}{% endif %}">
1
+ <div class="bg-white border flex flex-col flex-grow overflow-hidden p-6 relative rounded shadow-sm dark:bg-base-900 dark:border-base-800 {% if class %} {{ class }}{% endif %}">
2
2
  {% if title %}
3
3
  <h2 class="bg-base-50 border-b font-semibold mb-6 -mt-6 -mx-6 py-4 px-6 text-font-important-light dark:text-font-important-dark dark:border-base-800 dark:bg-white/[.02]">
4
4
  {{ title }}
@@ -1,31 +1,47 @@
1
- {% load unfold %}
1
+ {% load i18n unfold %}
2
2
 
3
- <div class="{% if card_included == 1 %}-m-6{% else %}lg:border lg:rounded lg:shadow-sm{% endif %} overflow-x-auto lg:dark:border-base-800">
4
- <table class="block border-base-200 border-spacing-none border-separate w-full lg:table">
5
- {% if table.headers %}
6
- <thead class="text-base-900 dark:text-base-100">
7
- <tr class="bg-base-50 dark:bg-white/[.02]">
8
- {% for header in table.headers %}
9
- <th class="align-middle font-semibold py-2 text-left text-sm whitespace-nowrap sortable column-description hidden px-3 lg:table-cell {% if card_included == 1 %}first:pl-6 last:pr-6{% endif %}">
10
- {{ header }}
11
- </th>
12
- {% endfor %}
13
- </tr>
14
- </thead>
15
- {% endif %}
3
+ <div class="flex flex-col">
4
+ {% if title %}
5
+ <h3 class="font-semibold mb-1 text-font-important-light text-sm dark:text-font-important-dark">
6
+ {{ title }}
7
+ </h3>
8
+ {% endif %}
9
+
10
+ <div class="{% if card_included == 1 %}-m-6{% else %} bg-white flex flex-col grow lg:border lg:border-base-200 lg:overflow-hidden lg:rounded lg:shadow-sm{% endif %} lg:dark:border-base-800 dark:bg-base-900">
11
+ <div {% if height %}style="max-height: {{ height }}px;" data-simplebar{% endif %}>
12
+ <table class="block border-spacing-none border-separate w-full lg:table">
13
+ {% if table.headers %}
14
+ <thead class="text-base-900 dark:text-base-100 {% if height %}sticky top-0{% endif %}">
15
+ <tr class="bg-base-50 dark:bg-white/[.02]">
16
+ {% for header in table.headers %}
17
+ <th class="align-middle border-b border-base-200 font-semibold py-2 text-left text-sm whitespace-nowrap sortable column-description hidden px-3 lg:table-cell dark:border-base-800 {% if card_included == 1 %}first:pl-6 last:pr-6{% endif %}">
18
+ {{ header|capfirst }}
19
+ </th>
20
+ {% endfor %}
21
+ </tr>
22
+ </thead>
23
+ {% endif %}
16
24
 
17
- {% if table.rows %}
18
- <tbody class="block lg:table-row-group">
19
- {% for row in table.rows %}
20
- <tr class="{% if striped == 1 %}{% cycle '' 'bg-base-50 dark:bg-white/[.02]' %}{% endif %} block {% if not card_included == 1 %}border mb-3 rounded shadow-sm{% else %}border-t{% endif %} lg:table-row lg:border-none lg:mb-0 lg:shadow-none dark:border-base-800">
21
- {% for cell in row %}
22
- <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:items-center before:mr-auto first:border-t-0 lg:before:hidden lg:first:border-t 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 %}>
23
- {{ cell }}
24
- </td>
25
+ {% 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 mb-3 rounded shadow-sm{% else %}border-b{% endif %} lg:table-row lg:border-none lg:mb-0 lg:shadow-none dark:border-base-800">
29
+ {% for cell in row %}
30
+ <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:items-center before:mr-auto first:border-t-0 lg:group-[.first-row]:border-t-0 lg:before:hidden lg:first:border-t 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 %}>
31
+ {{ cell }}
32
+ </td>
33
+ {% endfor %}
34
+ </tr>
25
35
  {% endfor %}
26
- </tr>
27
- {% endfor %}
28
- </tbody>
36
+ </tbody>
37
+ {% endif %}
38
+ </table>
39
+ </div>
40
+
41
+ {% if not table.rows %}
42
+ <p class="bg-white border border-base-200 flex grow items-center justify-center py-2 rounded shadow-sm dark:bg-base-900 lg:border-0 lg:rounded-none lg:shadow-none">
43
+ {% trans "No data" %}
44
+ </p>
29
45
  {% endif %}
30
- </table>
46
+ </div>
31
47
  </div>
@@ -22,7 +22,7 @@
22
22
  {% endif %}
23
23
 
24
24
  {% if cl.has_active_filters %}
25
- <a href="{{ cl.clear_all_filters_qs }}" class="border flex-grow font-medium px-3 py-2 rounded text-center transition-all w-full hover:bg-base-50 lg:w-auto dark:border-base-700 dark:hover:text-base-200">
25
+ <a href="{{ cl.clear_all_filters_qs }}" class="border flex-grow font-medium px-3 py-2 rounded text-center transition-all w-full lg:w-auto dark:border-base-700 dark:hover:text-base-200">
26
26
  {% trans "Clear all filters" %}
27
27
  </a>
28
28
  {% endif %}
@@ -0,0 +1,35 @@
1
+ {% load admin_urls i18n %}
2
+
3
+ {% url cl.opts|admin_urlname:"add" as add_url %}
4
+ {% blocktranslate with name=cl.opts.verbose_name asvar title %}Add {{ name }}{% endblocktranslate %}
5
+
6
+ <div class="bg-white border border-base-200 flex flex-col items-center px-8 py-24 rounded shadow-sm dark:bg-base-900 dark:border-base-800">
7
+ <div class="border border-base-300 border-dashed flex h-24 items-center justify-center mb-8 rounded-full w-24 dark:border-base-700">
8
+ <span class="material-symbols-outlined text-base-500 !text-5xl dark:text-base-400">inbox</span>
9
+ </div>
10
+
11
+ <h2 class="font-semibold mb-1 text-xl text-font-important-light tracking-tight dark:text-font-important-dark">
12
+ {% trans "No results found" %}
13
+ </h2>
14
+
15
+ <p class="mb-6 text-center">
16
+ {% trans "This page yielded into no results. Create a new item or reset your filters." %}
17
+ </p>
18
+
19
+ {% if has_add_permission or cl.has_filters %}
20
+ <div class="flex flex-col gap-4 justify-center w-full lg:flex-row">
21
+ {% if has_add_permission %}
22
+ <a href="{% add_preserved_filters add_url is_popup to_field %}" class="bg-primary-600 flex flex-row font-medium gap-2 items-center h-9.5 justify-center px-3 py-2 rounded text-white w-full lg:w-auto">
23
+ <span class="material-symbols-outlined text-white">add</span> {{ title }}
24
+ </a>
25
+ {% endif %}
26
+
27
+
28
+ {% if cl.has_filters %}
29
+ <a href="{{ cl.clear_all_filters_qs }}" class="border border-base-200 flex flex-row font-medium gap-2 group/button h-9.5 items-center justify-center px-3 py-2 rounded w-full hover:text-primary-600 lg:w-auto dark:border-base-700 dark:hover:bg-base-900 dark:hover:text-primary-500">
30
+ <span class="material-symbols-outlined ml-1">filter_list_off</span> {% trans "Reset filters" %}
31
+ </a>
32
+ {% endif %}
33
+ </div>
34
+ {% endif %}
35
+ </div>
@@ -1,10 +1,13 @@
1
1
  {% load unfold %}
2
2
 
3
- <li class="border-b flex-grow relative text-center md:border-b-0 md:border-r last:border-0 dark:border-base-700" {% if not link %}x-data="{actionDropdownOpen: false}"{% endif %}>
4
- <a {% if link %}href="{{ link }}"{% endif %}class="cursor-pointer flex items-center gap-2 px-4 py-2 text-left whitespace-nowrap hover:text-primary-600 dark:hover:text-primary-500"{% if blank %} target="_blank"{% endif %} {% include "unfold/helpers/attrs.html" with attrs=action.attrs %} {% if not link %}x-on:click="actionDropdownOpen = !actionDropdownOpen" x-bind:class="{'text-primary-600 dark:text-primary-500': actionDropdownOpen }"{% endif %}>
5
-
3
+ <li class="{% action_item_classes action %}" {% if not link %}x-data="{actionDropdownOpen: false}" x-ref="actionDropdown{{ action.method_name }}"{% endif %}>
4
+ <a {% if link %}href="{{ link }}"{% endif %}class="cursor-pointer flex items-center gap-2 px-3 py-2 text-left whitespace-nowrap" {% if blank %} target="_blank"{% endif %} {% include "unfold/helpers/attrs.html" with attrs=action.attrs %}
5
+ {% if not link %}
6
+ x-on:click="actionDropdownOpen = !actionDropdownOpen"
7
+ x-bind:class="{'{% if action.variant.value == "default" %}text-primary-600 dark:text-primary-500{% endif %}': actionDropdownOpen }"
8
+ {% endif %}>
6
9
  {% if action.icon %}
7
- <span class="material-symbols-outlined -ml-1">
10
+ <span class="material-symbols-outlined">
8
11
  {{ action.icon }}
9
12
  </span>
10
13
  {% endif %}
@@ -12,27 +15,29 @@
12
15
  {{ title }}
13
16
 
14
17
  {% if not link %}
15
- <span class="material-symbols-outlined -mr-1 rotate-90">
18
+ <span class="material-symbols-outlined ml-auto rotate-90">
16
19
  chevron_right
17
20
  </span>
18
21
  {% endif %}
19
22
  </a>
20
23
 
21
24
  {% if not link %}
22
- <nav class="absolute bg-white border flex flex-col -mr-px py-1 right-0 rounded shadow-lg top-10 w-52 z-50 dark:bg-base-800 dark:border-base-700" x-transition x-show="actionDropdownOpen" x-on:click.outside="actionDropdownOpen = false">
23
- {% for item in action.items %}
24
- <a href="{{ item.path }}" class="flex items-center font-normal gap-2 max-h-[30px] mx-1 px-3 py-2 rounded text-left hover:bg-base-100 hover:text-base-700 dark:hover:bg-base-700 dark:hover:text-base-200"{% if blank %} target="_blank"{% endif %} {% include "unfold/helpers/attrs.html" with attrs=action.attrs %}>
25
- {% if item.icon %}
26
- <span class="material-symbols-outlined -ml-1">
27
- {{ item.icon }}
28
- </span>
29
- {% endif %}
25
+ <template x-teleport="body">
26
+ <nav x-anchor.bottom-end.offset.4="$refs.actionDropdown{{ action.method_name }}" class="absolute bg-white border flex flex-col -mr-px py-1 right-0 rounded shadow-lg top-10 w-52 z-50 dark:bg-base-800 dark:border-base-700" x-transition x-show="actionDropdownOpen" x-on:click.outside="actionDropdownOpen = false">
27
+ {% for item in action.items %}
28
+ <a href="{{ item.path }}" class="flex items-center font-normal gap-2 max-h-[30px] mx-1 px-3 py-2 rounded text-left hover:bg-base-100 hover:text-base-700 dark:hover:bg-base-700 dark:hover:text-base-200"{% if blank %} target="_blank"{% endif %} {% include "unfold/helpers/attrs.html" with attrs=action.attrs %}>
29
+ {% if item.icon %}
30
+ <span class="material-symbols-outlined">
31
+ {{ item.icon }}
32
+ </span>
33
+ {% endif %}
30
34
 
31
- <span class="grow truncate">
32
- {{ item.title }}
33
- </span>
34
- </a>
35
- {% endfor %}
36
- </nav>
35
+ <span class="grow truncate">
36
+ {{ item.title }}
37
+ </span>
38
+ </a>
39
+ {% endfor %}
40
+ </nav>
41
+ </template>
37
42
  {% endif %}
38
43
  </li>
@@ -0,0 +1,19 @@
1
+ {% if actions_list or actions_detail or actions_items or nav_global %}
2
+ <ul class="flex flex-col font-medium mb-4 ml-auto mt-2 shadow-sm md:flex-row md:mb-2 md:mt-0 max-md:w-full">
3
+ {% if actions_items %}
4
+ {{ actions_items }}
5
+ {% endif %}
6
+
7
+ {% if nav_global %}
8
+ {{ nav_global }}
9
+ {% endif %}
10
+
11
+ {% for action in actions_list %}
12
+ {% include "unfold/helpers/tab_action.html" with title=action.title link=action.path %}
13
+ {% endfor %}
14
+
15
+ {% for action in actions_detail %}
16
+ {% include "unfold/helpers/tab_action.html" with title=action.title link=action.path %}
17
+ {% endfor %}
18
+ </ul>
19
+ {% endif %}
@@ -0,0 +1,47 @@
1
+ {% load i18n %}
2
+
3
+ {% if inlines_list or tabs_list %}
4
+ <ul class="border rounded flex flex-col max-md:w-full md:flex-row md:border-b-0 md:border-t-0 md:border-l-0 md:border-r-0 dark:border-base-800">
5
+ {% for item in tabs_list %}
6
+ {% if item.has_permission %}
7
+ <li class="border-b last:border-b-0 md:border-b-0 md:mr-8 dark:border-base-800">
8
+ <a href="{% if item.link_callback %}{{ item.link_callback }}{% else %}{{ item.link }}{% endif %}{% if item.inline %}#{{ item.inline }}{% endif %}"
9
+ class="block px-3 py-2 md:py-4 md:px-0 dark:border-base-800 {% if item.active and not item.inline %} border-b font-semibold -mb-px text-primary-600 hover:text-primary-600 dark:text-primary-500 dark:hover:text-primary-500 md:border-primary-500 dark:md:!border-primary-600{% else %}font-medium hover:text-primary-600 dark:hover:text-primary-500{% endif %}"
10
+ {% if item.inline %}
11
+ x-on:click="activeTab = '{{ item.inline }}'"
12
+ x-bind:class="{'border-b border-base-200 dark:border-base-800 md:border-primary-500 dark:md:!border-primary-600 font-semibold -mb-px text-primary-600 dark:text-primary-500': activeTab == '{{ item.inline }}'}"
13
+ {% endif %}
14
+ >
15
+ {{ item.title }}
16
+ </a>
17
+ </li>
18
+ {% endif %}
19
+ {% endfor %}
20
+
21
+ {% if inlines_list %}
22
+ <li class="border-b last:border-b-0 md:border-b-0 md:mr-8 dark:border-base-800">
23
+ <a class="block cursor-pointer font-medium px-3 py-2 md:py-4 md:px-0"
24
+ href="#general"
25
+ x-on:click="activeTab = 'general'"
26
+ x-bind:class="{'border-b border-base-200 dark:border-base-800 md:border-primary-500 dark:md:!border-primary-600 font-semibold -mb-px text-primary-600 dark:text-primary-500': activeTab == 'general', 'hover:text-primary-600 dark:hover:text-primary-500 dark:border-base-800': activeTab != 'general'}">
27
+ {% trans "General" %}
28
+ </a>
29
+ </li>
30
+
31
+ {% for inline in inlines_list %}
32
+ <li class="border-b last:border-b-0 md:border-b-0 md:mr-8 dark:border-base-800">
33
+ <a class="block cursor-pointer font-medium px-3 py-2 md:py-4 md:px-0"
34
+ href="#{{ inline.opts.verbose_name|slugify }}"
35
+ x-on:click="activeTab = '{{ inline.opts.verbose_name|slugify }}'"
36
+ x-bind:class="{'border-b border-base-200 dark:border-base-800 md:border-primary-500 dark:md:!border-primary-600 font-semibold -mb-px text-primary-600 dark:text-primary-500': activeTab == '{{ inline.opts.verbose_name|slugify }}', 'hover:text-primary-600 dark:hover:text-primary-500 dark:border-base-800': activeTab != '{{ inline.opts.verbose_name|slugify }}'}">
37
+ {% if inline.formset.max_num == 1 %}
38
+ {{ inline.opts.verbose_name|capfirst }}
39
+ {% else %}
40
+ {{ inline.opts.verbose_name_plural|capfirst }}
41
+ {% endif %}
42
+ </a>
43
+ </li>
44
+ {% endfor %}
45
+ {% endif %}
46
+ </ul>
47
+ {% endif %}
@@ -2,73 +2,10 @@
2
2
 
3
3
  {% if not is_popup %}
4
4
  {% if tabs_list or inlines_list or actions_list or actions_detail or actions_items or nav_global %}
5
- <div class="flex items-start flex-col mb-4 text-sm w-full md:border-b dark:md:border-base-800 md:border-l-0 md:flex-row md:items-center md:justify-end">
6
- {% if inlines_list or tabs_list %}
7
- <ul class="border rounded flex flex-col w-full md:flex-row md:border-b-0 md:border-t-0 md:border-l-0 md:border-r-0 dark:border-base-800">
8
- {% for item in tabs_list %}
9
- {% if item.has_permission %}
10
- <li class="border-b last:border-b-0 md:border-b-0 md:mr-8 dark:border-base-800">
11
- <a href="{% if item.link_callback %}{{ item.link_callback }}{% else %}{{ item.link }}{% endif %}"
12
- class="block px-3 py-2 md:py-4 md:px-0 dark:border-base-800 {% if item.active and not item.inline %} border-b font-semibold -mb-px text-primary-600 hover:text-primary-600 dark:text-primary-500 dark:hover:text-primary-500 md:border-primary-500 dark:md:!border-primary-600{% else %}font-medium hover:text-primary-600 dark:hover:text-primary-500{% endif %}"
13
- {% if item.inline %}
14
- x-on:click="activeTab = '{{ item.inline }}'"
15
- x-bind:class="{'border-b border-base-200 dark:border-base-800 md:border-primary-500 dark:md:!border-primary-600 font-semibold -mb-px text-primary-600 dark:text-primary-500': activeTab == '{{ item.inline }}'}"
16
- {% endif %}
17
- >
18
- {{ item.title }}
19
- </a>
20
- </li>
21
- {% endif %}
22
- {% endfor %}
5
+ <div class="flex items-start flex-col mb-4 md:border-b dark:md:border-base-800 md:border-l-0 md:flex-row md:items-center">
6
+ {% include "unfold/helpers/tab_items.html" %}
23
7
 
24
- {% if inlines_list %}
25
- <li class="border-b last:border-b-0 md:border-b-0 md:mr-8 dark:border-base-800">
26
- <a class="block cursor-pointer font-medium px-3 py-2 md:py-4 md:px-0"
27
- href="#general"
28
- x-on:click="activeTab = 'general'"
29
- x-bind:class="{'border-b border-base-200 dark:border-base-800 md:border-primary-500 dark:md:!border-primary-600 font-semibold -mb-px text-primary-600 dark:text-primary-500': activeTab == 'general', 'hover:text-primary-600 dark:hover:text-primary-500 dark:border-base-800': activeTab != 'general'}">
30
- {% trans "General" %}
31
- </a>
32
- </li>
33
-
34
- {% for inline in inlines_list %}
35
- <li class="border-b last:border-b-0 md:border-b-0 md:mr-8 dark:border-base-800">
36
- <a class="block cursor-pointer font-medium px-3 py-2 md:py-4 md:px-0"
37
- href="#{{ inline.opts.verbose_name|slugify }}"
38
- x-on:click="activeTab = '{{ inline.opts.verbose_name|slugify }}'"
39
- x-bind:class="{'border-b border-base-200 dark:border-base-800 md:border-primary-500 dark:md:!border-primary-600 font-semibold -mb-px text-primary-600 dark:text-primary-500': activeTab == '{{ inline.opts.verbose_name|slugify }}', 'hover:text-primary-600 dark:hover:text-primary-500 dark:border-base-800': activeTab != '{{ inline.opts.verbose_name|slugify }}'}">
40
- {% if inline.formset.max_num == 1 %}
41
- {{ inline.opts.verbose_name|capfirst }}
42
- {% else %}
43
- {{ inline.opts.verbose_name_plural|capfirst }}
44
- {% endif %}
45
- </a>
46
- </li>
47
- {% endfor %}
48
- {% endif %}
49
- </ul>
50
- {% endif %}
51
-
52
- {% if actions_list or actions_detail or actions_items or nav_global %}
53
- <ul class="border flex flex-col font-medium mb-4 mt-2 rounded shadow-sm md:flex-row md:mb-2 md:mt-0 dark:border-base-700 max-md:w-full">
54
- {% for action in actions_list %}
55
- {% include "unfold/helpers/tab_action.html" with title=action.title link=action.path %}
56
- {% endfor %}
57
-
58
- {% for action in actions_detail %}
59
- {% include "unfold/helpers/tab_action.html" with title=action.title link=action.path %}
60
- {% endfor %}
61
-
62
- {% if actions_items %}
63
- {{ actions_items }}
64
- {% endif %}
65
-
66
- {% if nav_global %}
67
- {{ nav_global }}
68
- {% endif %}
69
- </ul>
70
- {% endif %}
8
+ {% include "unfold/helpers/tab_actions.html" %}
71
9
  </div>
72
10
  {% endif %}
73
-
74
11
  {% endif %}
@@ -14,9 +14,8 @@
14
14
 
15
15
  <!DOCTYPE html>
16
16
  <html lang="{{ LANGUAGE_CODE|default:"en-us" }}" dir="{{ LANGUAGE_BIDI|yesno:"rtl,ltr,auto" }}" {% if theme %}class="{{ theme }}"{% endif %} x-data="{ adminTheme: {% if theme %}'{{ theme }}'{% else %}$persist('auto').as('adminTheme'){% endif %} }" x-bind:class="{'dark': adminTheme === 'dark' || (adminTheme === 'auto' && window.matchMedia('(prefers-color-scheme: dark)').matches)}" x-cloak>
17
-
18
17
  <head>
19
- <title>{% block title %}{% endblock %}</title>
18
+ <title>{{ environment_title_prefix|default:"" }} {% block title %}{% endblock %}</title>
20
19
 
21
20
  <link href="{% static "unfold/fonts/inter/styles.css" %}" rel="stylesheet">
22
21
  <link href="{% static "unfold/fonts/material-symbols/styles.css" %}" rel="stylesheet">