vanta-admin 0.5.0__tar.gz → 0.6.0__tar.gz

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 (44) hide show
  1. {vanta_admin-0.5.0/vanta_admin.egg-info → vanta_admin-0.6.0}/PKG-INFO +6 -1
  2. {vanta_admin-0.5.0 → vanta_admin-0.6.0}/README.md +5 -0
  3. {vanta_admin-0.5.0 → vanta_admin-0.6.0}/pyproject.toml +1 -1
  4. {vanta_admin-0.5.0 → vanta_admin-0.6.0}/vanta_admin/static/admin/css/theme.admin.css +284 -3
  5. {vanta_admin-0.5.0 → vanta_admin-0.6.0}/vanta_admin/static/admin/css/theme.base.css +31 -13
  6. {vanta_admin-0.5.0 → vanta_admin-0.6.0}/vanta_admin/static/admin/css/theme.login.css +8 -8
  7. {vanta_admin-0.5.0 → vanta_admin-0.6.0}/vanta_admin/static/admin/css/two_factor.css +25 -10
  8. {vanta_admin-0.5.0 → vanta_admin-0.6.0}/vanta_admin/static/admin/js/forms.js +50 -0
  9. {vanta_admin-0.5.0 → vanta_admin-0.6.0}/vanta_admin/static/admin/js/sidebar.js +79 -17
  10. {vanta_admin-0.5.0 → vanta_admin-0.6.0}/vanta_admin/templates/admin/includes/breadcrumbs.html +1 -1
  11. {vanta_admin-0.5.0 → vanta_admin-0.6.0/vanta_admin.egg-info}/PKG-INFO +6 -1
  12. {vanta_admin-0.5.0 → vanta_admin-0.6.0}/LICENSE +0 -0
  13. {vanta_admin-0.5.0 → vanta_admin-0.6.0}/MANIFEST.in +0 -0
  14. {vanta_admin-0.5.0 → vanta_admin-0.6.0}/setup.cfg +0 -0
  15. {vanta_admin-0.5.0 → vanta_admin-0.6.0}/vanta_admin/__init__.py +0 -0
  16. {vanta_admin-0.5.0 → vanta_admin-0.6.0}/vanta_admin/apps.py +0 -0
  17. {vanta_admin-0.5.0 → vanta_admin-0.6.0}/vanta_admin/static/admin/css/theme.css +0 -0
  18. {vanta_admin-0.5.0 → vanta_admin-0.6.0}/vanta_admin/static/admin/css/theme.responsive.css +0 -0
  19. {vanta_admin-0.5.0 → vanta_admin-0.6.0}/vanta_admin/static/admin/css/theme.tokens.css +0 -0
  20. {vanta_admin-0.5.0 → vanta_admin-0.6.0}/vanta_admin/static/admin/img/favicon.png +0 -0
  21. {vanta_admin-0.5.0 → vanta_admin-0.6.0}/vanta_admin/static/admin/js/changelist.js +0 -0
  22. {vanta_admin-0.5.0 → vanta_admin-0.6.0}/vanta_admin/static/admin/js/messages.js +0 -0
  23. {vanta_admin-0.5.0 → vanta_admin-0.6.0}/vanta_admin/static/admin/js/vanta-theme.js +0 -0
  24. {vanta_admin-0.5.0 → vanta_admin-0.6.0}/vanta_admin/templates/admin/app_index.html +0 -0
  25. {vanta_admin-0.5.0 → vanta_admin-0.6.0}/vanta_admin/templates/admin/base.html +0 -0
  26. {vanta_admin-0.5.0 → vanta_admin-0.6.0}/vanta_admin/templates/admin/base_site.html +0 -0
  27. {vanta_admin-0.5.0 → vanta_admin-0.6.0}/vanta_admin/templates/admin/change_form.html +0 -0
  28. {vanta_admin-0.5.0 → vanta_admin-0.6.0}/vanta_admin/templates/admin/change_list.html +0 -0
  29. {vanta_admin-0.5.0 → vanta_admin-0.6.0}/vanta_admin/templates/admin/filter.html +0 -0
  30. {vanta_admin-0.5.0 → vanta_admin-0.6.0}/vanta_admin/templates/admin/index.html +0 -0
  31. {vanta_admin-0.5.0 → vanta_admin-0.6.0}/vanta_admin/templates/admin/login.html +0 -0
  32. {vanta_admin-0.5.0 → vanta_admin-0.6.0}/vanta_admin/templates/admin/nav_sidebar.html +0 -0
  33. {vanta_admin-0.5.0 → vanta_admin-0.6.0}/vanta_admin/templates/two_factor/_base.html +0 -0
  34. {vanta_admin-0.5.0 → vanta_admin-0.6.0}/vanta_admin/templates/two_factor/_base_focus.html +0 -0
  35. {vanta_admin-0.5.0 → vanta_admin-0.6.0}/vanta_admin/templates/two_factor/_wizard_actions.html +0 -0
  36. {vanta_admin-0.5.0 → vanta_admin-0.6.0}/vanta_admin/templates/two_factor/core/login.html +0 -0
  37. {vanta_admin-0.5.0 → vanta_admin-0.6.0}/vanta_admin/templates/two_factor/core/setup_complete.html +0 -0
  38. {vanta_admin-0.5.0 → vanta_admin-0.6.0}/vanta_admin/templates/two_factor/profile/profile.html +0 -0
  39. {vanta_admin-0.5.0 → vanta_admin-0.6.0}/vanta_admin/templatetags/__init__.py +0 -0
  40. {vanta_admin-0.5.0 → vanta_admin-0.6.0}/vanta_admin/templatetags/admin_theme.py +0 -0
  41. {vanta_admin-0.5.0 → vanta_admin-0.6.0}/vanta_admin.egg-info/SOURCES.txt +0 -0
  42. {vanta_admin-0.5.0 → vanta_admin-0.6.0}/vanta_admin.egg-info/dependency_links.txt +0 -0
  43. {vanta_admin-0.5.0 → vanta_admin-0.6.0}/vanta_admin.egg-info/requires.txt +0 -0
  44. {vanta_admin-0.5.0 → vanta_admin-0.6.0}/vanta_admin.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vanta-admin
3
- Version: 0.5.0
3
+ Version: 0.6.0
4
4
  Summary: A sharp, dark, and portable Django admin theme forged for builders who want the admin to feel less default.
5
5
  Author: oli-dev0
6
6
  License-Expression: MIT
@@ -42,6 +42,10 @@ feels more deliberate in everyday use.
42
42
  two-factor auth pages.
43
43
  - Denser dashboards, tables, filters, messages, and forms so admin work feels
44
44
  easier to scan.
45
+ - Better edit pages for longer admin forms, with save buttons that stay easier
46
+ to reach while scrolling.
47
+ - Cleaner buttons for saving, deleting, viewing history, and changing passwords.
48
+ - Clearer form errors on admin pages, login, and two-factor auth screens.
45
49
  - Admin messages that appear clearly, pause while hovered, and dismiss
46
50
  themselves after a short time.
47
51
  - Changelist actions that appear only when rows are selected, stay available
@@ -55,6 +59,7 @@ feels more deliberate in everyday use.
55
59
  stored in the browser for each admin user.
56
60
  - Shorter changelist date formatting that makes date-heavy tables easier to
57
61
  scan.
62
+ - Simpler breadcrumbs on edit pages, so the page path feels less cluttered.
58
63
  - A packaged favicon for the admin.
59
64
  - A mobile sidebar overlay that makes the admin easier to use on smaller
60
65
  screens.
@@ -17,6 +17,10 @@ feels more deliberate in everyday use.
17
17
  two-factor auth pages.
18
18
  - Denser dashboards, tables, filters, messages, and forms so admin work feels
19
19
  easier to scan.
20
+ - Better edit pages for longer admin forms, with save buttons that stay easier
21
+ to reach while scrolling.
22
+ - Cleaner buttons for saving, deleting, viewing history, and changing passwords.
23
+ - Clearer form errors on admin pages, login, and two-factor auth screens.
20
24
  - Admin messages that appear clearly, pause while hovered, and dismiss
21
25
  themselves after a short time.
22
26
  - Changelist actions that appear only when rows are selected, stay available
@@ -30,6 +34,7 @@ feels more deliberate in everyday use.
30
34
  stored in the browser for each admin user.
31
35
  - Shorter changelist date formatting that makes date-heavy tables easier to
32
36
  scan.
37
+ - Simpler breadcrumbs on edit pages, so the page path feels less cluttered.
33
38
  - A packaged favicon for the admin.
34
39
  - A mobile sidebar overlay that makes the admin easier to use on smaller
35
40
  screens.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "vanta-admin"
7
- version = "0.5.0"
7
+ version = "0.6.0"
8
8
  description = "A sharp, dark, and portable Django admin theme forged for builders who want the admin to feel less default."
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -329,15 +329,19 @@ html[data-theme="light"] #nav-sidebar .admin-sidebar__filter input[type="search"
329
329
  gap: 0.75rem;
330
330
  width: 100%;
331
331
  padding: 0.7rem 0.78rem;
332
- border: 1px solid var(--border);
332
+ border: 1px solid var(--border-strong);
333
333
  border-radius: var(--radius-md);
334
- background: var(--surface-muted);
334
+ background: rgba(39, 49, 51, 0.96);
335
335
  box-shadow: none;
336
336
  color: var(--heading);
337
337
  cursor: pointer;
338
338
  list-style: none;
339
339
  }
340
340
 
341
+ html[data-theme="light"] .admin-sidebar__account-summary {
342
+ background: #f7fbff;
343
+ }
344
+
341
345
  .admin-sidebar__account-icon {
342
346
  width: 1.15rem;
343
347
  height: 1.15rem;
@@ -965,7 +969,238 @@ fieldset.module,
965
969
  }
966
970
 
967
971
  .submit-row {
968
- background: var(--accent-soft);
972
+ display: flex;
973
+ flex-wrap: wrap;
974
+ align-items: center;
975
+ gap: 0.55rem;
976
+ background: color-mix(in srgb, var(--admin-surface-raised) 74%, var(--admin-accent-softer));
977
+ }
978
+
979
+ .change-form .submit-row.is-sticky-submit-row {
980
+ position: fixed;
981
+ right: auto;
982
+ bottom: 0.75rem;
983
+ left: var(--admin-submit-row-fixed-left, 0.75rem);
984
+ z-index: 30;
985
+ width: var(--admin-submit-row-fixed-width, auto);
986
+ }
987
+
988
+ .admin-submit-row-placeholder {
989
+ height: 0;
990
+ }
991
+
992
+ .change-form .errornote,
993
+ .change-form ul.errorlist {
994
+ --admin-inline-error-icon: var(--admin-error-text);
995
+ border: 1px solid var(--admin-error-border);
996
+ border-left: 4px solid var(--admin-error-border);
997
+ border-radius: var(--admin-radius-sm);
998
+ background: var(--admin-error-bg);
999
+ color: var(--admin-error-text);
1000
+ box-shadow: none;
1001
+ }
1002
+
1003
+ .change-form .errornote {
1004
+ margin: 0 0 0.75rem;
1005
+ }
1006
+
1007
+ .change-form ul.errorlist {
1008
+ color: var(--admin-error-text);
1009
+ }
1010
+
1011
+ .change-form ul.errorlist li {
1012
+ color: var(--admin-error-text);
1013
+ }
1014
+
1015
+ .change-form .submit-row input,
1016
+ .change-form .submit-row a.deletelink {
1017
+ display: inline-flex;
1018
+ align-items: center;
1019
+ justify-content: center;
1020
+ min-height: 2.35rem;
1021
+ margin: 0;
1022
+ padding: 0.54rem 0.82rem;
1023
+ border: 1px solid var(--admin-border-strong);
1024
+ border-radius: var(--admin-radius-sm);
1025
+ background: var(--admin-surface-muted);
1026
+ color: var(--admin-text);
1027
+ font: inherit;
1028
+ font-size: 0.88rem;
1029
+ font-weight: 760;
1030
+ line-height: 1.2;
1031
+ text-decoration: none;
1032
+ box-shadow: none;
1033
+ cursor: pointer;
1034
+ transition:
1035
+ background-color var(--admin-transition),
1036
+ border-color var(--admin-transition),
1037
+ color var(--admin-transition),
1038
+ filter var(--admin-transition),
1039
+ transform var(--admin-transition);
1040
+ }
1041
+
1042
+ .change-form .submit-row input:not(.default):not([name="_save"]):not(:first-child) {
1043
+ border-color: var(--admin-border-strong);
1044
+ background: var(--admin-surface);
1045
+ color: var(--admin-text);
1046
+ }
1047
+
1048
+ .submit-row input.default,
1049
+ .change-form .submit-row input[name="_save"],
1050
+ .change-form .submit-row input[type="submit"]:first-child {
1051
+ border-color: var(--admin-accent-border);
1052
+ background: var(--admin-accent);
1053
+ color: var(--admin-button-text);
1054
+ }
1055
+
1056
+ .change-form .submit-row a.deletelink {
1057
+ margin-left: auto;
1058
+ border-color: var(--admin-error);
1059
+ background: var(--admin-error-bg);
1060
+ color: var(--admin-error-text);
1061
+ }
1062
+
1063
+ .change-form .submit-row input:hover,
1064
+ .change-form .submit-row input:focus-visible,
1065
+ .change-form .submit-row a.deletelink:hover,
1066
+ .change-form .submit-row a.deletelink:focus-visible {
1067
+ border-color: var(--admin-accent-border);
1068
+ background: var(--admin-surface-hover);
1069
+ color: var(--admin-text);
1070
+ filter: brightness(1.04);
1071
+ transform: translateY(-2px);
1072
+ }
1073
+
1074
+ .submit-row input.default:hover,
1075
+ .submit-row input.default:focus-visible,
1076
+ .change-form .submit-row input[name="_save"]:hover,
1077
+ .change-form .submit-row input[name="_save"]:focus-visible,
1078
+ .change-form .submit-row input[type="submit"]:first-child:hover,
1079
+ .change-form .submit-row input[type="submit"]:first-child:focus-visible {
1080
+ background: var(--admin-accent-strong);
1081
+ color: var(--admin-button-text);
1082
+ }
1083
+
1084
+ .change-form .submit-row a.deletelink:hover,
1085
+ .change-form .submit-row a.deletelink:focus-visible {
1086
+ border-color: var(--admin-error);
1087
+ background: var(--admin-error-bg);
1088
+ color: var(--admin-error-text);
1089
+ filter: brightness(1.04);
1090
+ }
1091
+
1092
+ .change-form .submit-row input:active,
1093
+ .change-form .submit-row a.deletelink:active {
1094
+ transform: translateY(0);
1095
+ }
1096
+
1097
+ .change-form .submit-row input:disabled {
1098
+ opacity: 0.62;
1099
+ cursor: not-allowed;
1100
+ transform: none;
1101
+ }
1102
+
1103
+ .delete-confirmation form {
1104
+ display: flex;
1105
+ flex-wrap: wrap;
1106
+ align-items: center;
1107
+ gap: 0.55rem;
1108
+ margin-top: 1rem;
1109
+ }
1110
+
1111
+ .delete-confirmation form input[type="submit"],
1112
+ .delete-confirmation form .cancel-link {
1113
+ display: inline-flex;
1114
+ align-items: center;
1115
+ justify-content: center;
1116
+ min-height: 2.35rem;
1117
+ margin: 0;
1118
+ padding: 0.54rem 0.82rem;
1119
+ border: 1px solid var(--admin-border-strong);
1120
+ border-radius: var(--admin-radius-sm);
1121
+ background: var(--admin-surface);
1122
+ color: var(--admin-text);
1123
+ font: inherit;
1124
+ font-size: 0.88rem;
1125
+ font-weight: 760;
1126
+ line-height: 1.2;
1127
+ text-decoration: none;
1128
+ box-shadow: none;
1129
+ cursor: pointer;
1130
+ transition:
1131
+ background-color var(--admin-transition),
1132
+ border-color var(--admin-transition),
1133
+ color var(--admin-transition),
1134
+ filter var(--admin-transition),
1135
+ transform var(--admin-transition);
1136
+ }
1137
+
1138
+ .delete-confirmation form input[type="submit"] {
1139
+ border-color: var(--admin-error-border-soft);
1140
+ background: var(--admin-error-surface);
1141
+ color: var(--admin-error-copy);
1142
+ }
1143
+
1144
+ .delete-confirmation form .cancel-link {
1145
+ border-color: var(--admin-accent-border);
1146
+ background: var(--admin-accent);
1147
+ color: var(--admin-button-text);
1148
+ }
1149
+
1150
+ .delete-confirmation form input[type="submit"]:hover,
1151
+ .delete-confirmation form input[type="submit"]:focus-visible {
1152
+ border-color: var(--admin-error);
1153
+ background: var(--admin-error-bg);
1154
+ color: var(--admin-error-text);
1155
+ filter: brightness(1.04);
1156
+ transform: translateY(-2px);
1157
+ }
1158
+
1159
+ .delete-confirmation form .cancel-link:hover,
1160
+ .delete-confirmation form .cancel-link:focus-visible {
1161
+ filter: brightness(1.04);
1162
+ transform: translateY(-2px);
1163
+ }
1164
+
1165
+ .delete-confirmation form .cancel-link:hover,
1166
+ .delete-confirmation form .cancel-link:focus-visible {
1167
+ background: var(--admin-accent-strong);
1168
+ color: var(--admin-button-text);
1169
+ }
1170
+
1171
+ .delete-confirmation form input[type="submit"]:active,
1172
+ .delete-confirmation form .cancel-link:active {
1173
+ transform: translateY(0);
1174
+ }
1175
+
1176
+ .field-password a.button[role="button"] {
1177
+ display: inline-flex;
1178
+ align-items: center;
1179
+ justify-content: center;
1180
+ min-height: 1.82rem;
1181
+ padding: 0.26rem 0.5rem;
1182
+ border: 1px solid var(--admin-accent-border);
1183
+ border-radius: var(--admin-radius-sm);
1184
+ background: var(--admin-accent);
1185
+ color: var(--admin-button-text);
1186
+ font-size: 0.88rem;
1187
+ font-weight: 760;
1188
+ line-height: 1.2;
1189
+ text-decoration: none;
1190
+ box-shadow: none;
1191
+ transition:
1192
+ background-color var(--admin-transition),
1193
+ color var(--admin-transition),
1194
+ filter var(--admin-transition),
1195
+ transform var(--admin-transition);
1196
+ }
1197
+
1198
+ .field-password a.button[role="button"]:hover,
1199
+ .field-password a.button[role="button"]:focus-visible {
1200
+ background: var(--admin-accent-strong);
1201
+ color: var(--admin-button-text);
1202
+ filter: brightness(1.04);
1203
+ transform: translateY(-2px);
969
1204
  }
970
1205
 
971
1206
  #changelist #toolbar,
@@ -1537,6 +1772,52 @@ input[type="checkbox"]:focus-visible {
1537
1772
  list-style: none;
1538
1773
  }
1539
1774
 
1775
+ .change-form .object-tools {
1776
+ position: relative;
1777
+ z-index: 5;
1778
+ overflow: visible;
1779
+ }
1780
+
1781
+ .change-form .object-tools li {
1782
+ overflow: visible;
1783
+ }
1784
+
1785
+ .change-form .object-tools a {
1786
+ position: relative;
1787
+ z-index: 1;
1788
+ display: inline-flex;
1789
+ align-items: center;
1790
+ justify-content: center;
1791
+ min-height: 1.82rem;
1792
+ padding: 0.26rem 0.5rem;
1793
+ border: 1px solid var(--admin-accent-border);
1794
+ border-radius: var(--admin-radius-sm);
1795
+ background: var(--admin-accent);
1796
+ color: var(--admin-button-text);
1797
+ font-size: 0.88rem;
1798
+ font-weight: 760;
1799
+ line-height: 1.2;
1800
+ text-decoration: none;
1801
+ box-shadow: none;
1802
+ transition:
1803
+ background-color var(--admin-transition),
1804
+ color var(--admin-transition),
1805
+ filter var(--admin-transition),
1806
+ transform var(--admin-transition);
1807
+ }
1808
+
1809
+ .change-form .object-tools a:hover,
1810
+ .change-form .object-tools a:focus-visible {
1811
+ background: var(--admin-accent-strong);
1812
+ color: var(--admin-button-text);
1813
+ filter: brightness(1.04);
1814
+ transform: translateY(-2px);
1815
+ }
1816
+
1817
+ #changelist .object-tools {
1818
+ margin-left: auto;
1819
+ }
1820
+
1540
1821
  #changelist .object-tools a,
1541
1822
  #changelist .object-tools a.addlink {
1542
1823
  display: inline-grid;
@@ -229,17 +229,19 @@ ul.messagelist li.is-removing {
229
229
  }
230
230
 
231
231
  .errornote,
232
- ul.errorlist,
233
- ul.errorlist li {
234
- width: 100%;
232
+ ul.errorlist {
233
+ --admin-message-accent: var(--admin-error);
234
+ --admin-inline-error-icon: color-mix(in srgb, var(--admin-message-accent) 72%, #fff);
235
+ --admin-inline-error-bg: color-mix(in srgb, var(--admin-message-accent) 38%, var(--admin-surface-raised));
236
+ width: fit-content;
235
237
  max-width: 100%;
236
238
  margin: 0 0 0.65rem;
237
239
  padding: 0.22rem 0.75rem;
238
- border: 1px solid var(--admin-error-border-soft);
239
- border-left: 4px solid var(--admin-error);
240
+ border: 1px solid var(--admin-border);
241
+ border-left: 4px solid var(--admin-message-accent);
240
242
  border-radius: var(--admin-radius-lg);
241
- background: var(--admin-error-surface);
242
- color: var(--admin-error-copy);
243
+ background: var(--admin-inline-error-bg);
244
+ color: var(--admin-text);
243
245
  box-shadow: none;
244
246
  font-size: 0.92rem;
245
247
  font-family: var(--site-font-body);
@@ -265,17 +267,17 @@ ul.errorlist::before {
265
267
  place-items: center;
266
268
  width: 0.9rem;
267
269
  height: 0.9rem;
268
- border: 2px solid color-mix(in srgb, var(--admin-error) 90%, #fff);
270
+ border: 2px solid var(--admin-inline-error-icon);
269
271
  border-radius: 999px;
270
- background: var(--admin-error-icon-bg);
271
- color: color-mix(in srgb, var(--admin-error) 82%, #fff);
272
+ background: transparent;
273
+ color: var(--admin-inline-error-icon);
272
274
  font-size: 0.62rem;
273
275
  font-weight: 800;
274
276
  line-height: 1;
275
277
  }
276
278
 
277
279
  .errornote {
278
- color: var(--admin-error-text-strong);
280
+ color: var(--admin-text);
279
281
  font-weight: 720;
280
282
  }
281
283
 
@@ -284,18 +286,29 @@ ul.errorlist {
284
286
  gap: 0.45rem;
285
287
  }
286
288
 
289
+ form .aligned ul.errorlist {
290
+ padding-left: 2.25rem;
291
+ }
292
+
287
293
  ul.errorlist li {
294
+ width: auto;
295
+ max-width: none;
288
296
  margin: 0;
289
297
  padding: 0;
290
298
  border: 0;
291
299
  border-radius: 0;
292
300
  background: transparent;
293
- color: var(--admin-error-copy);
301
+ color: var(--admin-text);
294
302
  font-size: 0.86rem;
295
303
  font-weight: 600;
296
304
  line-height: 1.4;
297
305
  }
298
306
 
307
+ html[data-theme="light"] .errornote,
308
+ html[data-theme="light"] ul.errorlist {
309
+ --admin-inline-error-bg: color-mix(in srgb, var(--admin-message-accent) 28%, #fff);
310
+ }
311
+
299
312
  .form-row.errors {
300
313
  border: 0;
301
314
  background: transparent;
@@ -362,13 +375,18 @@ td ul.errorlist + textarea {
362
375
  }
363
376
 
364
377
  .admin-breadcrumbs a,
365
- .breadcrumbs a {
378
+ .breadcrumbs a,
379
+ .admin-breadcrumb-app {
366
380
  display: inline-flex;
367
381
  align-items: center;
368
382
  border-radius: var(--admin-radius-sm);
369
383
  color: var(--admin-muted);
370
384
  font-weight: 650;
371
385
  padding: 0.2rem 0.5rem;
386
+ }
387
+
388
+ .admin-breadcrumbs a,
389
+ .breadcrumbs a {
372
390
  transition: background-color 160ms ease, color 160ms ease;
373
391
  }
374
392
 
@@ -202,19 +202,19 @@ body.login .errornote {
202
202
 
203
203
  body.login .errornote,
204
204
  body.login ul.errorlist.nonfield {
205
- border-color: var(--admin-error-border-soft);
206
- border-left-color: var(--admin-error);
207
- background: var(--admin-error-surface);
208
- color: var(--admin-error-copy);
205
+ border-color: var(--admin-border);
206
+ border-left-color: var(--admin-message-accent);
207
+ background: var(--admin-inline-error-bg);
208
+ color: var(--admin-text);
209
209
  }
210
210
 
211
211
  body.login .errornote::before,
212
212
  body.login ul.errorlist.nonfield::before {
213
- border-color: var(--admin-error-border-soft);
214
- background: var(--admin-error-icon-bg);
215
- color: var(--admin-error-text-strong);
213
+ border-color: var(--admin-inline-error-icon);
214
+ background: transparent;
215
+ color: var(--admin-inline-error-icon);
216
216
  }
217
217
 
218
218
  body.login ul.errorlist.nonfield li {
219
- color: var(--admin-error-copy);
219
+ color: var(--admin-text);
220
220
  }
@@ -392,16 +392,19 @@ body.vanta-two-factor {
392
392
  .vanta-two-factor .alert,
393
393
  .vanta-two-factor .errornote,
394
394
  .vanta-two-factor ul.errorlist {
395
+ --admin-message-accent: var(--admin-error);
396
+ --admin-inline-error-icon: color-mix(in srgb, var(--admin-message-accent) 72%, #fff);
397
+ --admin-inline-error-bg: color-mix(in srgb, var(--admin-message-accent) 38%, var(--admin-surface-raised));
395
398
  position: relative;
396
- width: 100%;
399
+ width: fit-content;
397
400
  max-width: 100%;
398
401
  margin: 0 0 0.75rem;
399
402
  padding: 0.22rem 0.75rem 0.22rem 2.25rem;
400
- border: 1px solid var(--admin-error-border-soft);
401
- border-left: 4px solid var(--admin-error);
403
+ border: 1px solid var(--admin-border);
404
+ border-left: 4px solid var(--admin-message-accent);
402
405
  border-radius: var(--admin-radius-lg);
403
- background: var(--admin-error-surface);
404
- color: var(--admin-error-copy);
406
+ background: var(--admin-inline-error-bg);
407
+ color: var(--admin-text);
405
408
  font-size: 0.9rem;
406
409
  font-family: var(--site-font-body);
407
410
  font-weight: 560;
@@ -410,6 +413,12 @@ body.vanta-two-factor {
410
413
  overflow-wrap: anywhere;
411
414
  }
412
415
 
416
+ html[data-theme="light"] .vanta-two-factor .alert,
417
+ html[data-theme="light"] .vanta-two-factor .errornote,
418
+ html[data-theme="light"] .vanta-two-factor ul.errorlist {
419
+ --admin-inline-error-bg: color-mix(in srgb, var(--admin-message-accent) 28%, #fff);
420
+ }
421
+
413
422
  .vanta-two-factor .alert::before,
414
423
  .vanta-two-factor .errornote::before,
415
424
  .vanta-two-factor ul.errorlist::before {
@@ -421,26 +430,32 @@ body.vanta-two-factor {
421
430
  place-items: center;
422
431
  width: 0.9rem;
423
432
  height: 0.9rem;
424
- border: 2px solid color-mix(in srgb, var(--admin-error) 90%, #fff);
433
+ border: 2px solid var(--admin-inline-error-icon);
425
434
  border-radius: 999px;
426
- background: var(--admin-error-icon-bg);
427
- color: color-mix(in srgb, var(--admin-error) 82%, #fff);
435
+ background: transparent;
436
+ color: var(--admin-inline-error-icon);
428
437
  font-size: 0.62rem;
429
438
  font-weight: 800;
430
439
  line-height: 1;
431
440
  }
432
441
 
433
442
  .vanta-two-factor .errornote {
434
- color: var(--admin-error-text-strong);
443
+ color: var(--admin-text);
435
444
  font-weight: 720;
436
445
  }
437
446
 
447
+ .vanta-two-factor ul.errorlist {
448
+ padding-left: 3rem;
449
+ }
450
+
438
451
  .vanta-two-factor ul.errorlist li {
452
+ width: auto;
453
+ max-width: none;
439
454
  margin: 0;
440
455
  padding: 0;
441
456
  border: 0;
442
457
  background: transparent;
443
- color: var(--admin-error-copy);
458
+ color: var(--admin-text);
444
459
  font-size: 0.84rem;
445
460
  font-weight: 600;
446
461
  line-height: 1.4;
@@ -169,6 +169,56 @@
169
169
  .map(enhanceActionSelect)
170
170
  .filter(Boolean);
171
171
 
172
+ function syncStickySubmitRow() {
173
+ const form = document.querySelector('body.change-form form[id$="_form"]');
174
+ const submitRow = form?.querySelector('.submit-row:last-of-type');
175
+
176
+ if (!form || !submitRow) {
177
+ return;
178
+ }
179
+
180
+ let placeholder = submitRow.nextElementSibling;
181
+ if (!placeholder || !placeholder.classList.contains('admin-submit-row-placeholder')) {
182
+ placeholder = document.createElement('div');
183
+ placeholder.className = 'admin-submit-row-placeholder';
184
+ submitRow.after(placeholder);
185
+ }
186
+
187
+ function syncSubmitRowPosition() {
188
+ const formRect = form.getBoundingClientRect();
189
+ const rowRect = submitRow.getBoundingClientRect();
190
+ const isFixed = submitRow.classList.contains('is-sticky-submit-row');
191
+ const naturalTop = isFixed
192
+ ? placeholder.getBoundingClientRect().top
193
+ : rowRect.top;
194
+ const shouldStick = naturalTop + rowRect.height > window.innerHeight && formRect.top < window.innerHeight;
195
+
196
+ submitRow.classList.toggle('is-sticky-submit-row', shouldStick);
197
+ form.classList.toggle('has-sticky-submit-row', shouldStick);
198
+
199
+ if (shouldStick) {
200
+ const contentRect = form.getBoundingClientRect();
201
+ const left = Math.max(contentRect.left, 12);
202
+ const rightEdge = Math.min(contentRect.right, window.innerWidth - 12);
203
+ const width = Math.max(rightEdge - left, 0);
204
+
205
+ submitRow.style.setProperty('--admin-submit-row-fixed-left', `${left}px`);
206
+ submitRow.style.setProperty('--admin-submit-row-fixed-width', `${width}px`);
207
+ placeholder.style.height = `${rowRect.height}px`;
208
+ } else {
209
+ submitRow.style.removeProperty('--admin-submit-row-fixed-left');
210
+ submitRow.style.removeProperty('--admin-submit-row-fixed-width');
211
+ placeholder.style.height = '0px';
212
+ }
213
+ }
214
+
215
+ window.addEventListener('scroll', syncSubmitRowPosition, { passive: true });
216
+ window.addEventListener('resize', syncSubmitRowPosition);
217
+ syncSubmitRowPosition();
218
+ }
219
+
220
+ syncStickySubmitRow();
221
+
172
222
  document.addEventListener('keydown', (event) => {
173
223
  if (event.key === 'Escape') {
174
224
  customDropdowns.forEach((dropdown) => {
@@ -13,7 +13,9 @@
13
13
  const navFilter = document.getElementById('admin-nav-filter');
14
14
  const mobileQuery = window.matchMedia('(max-width: 1024px)');
15
15
  const navSections = Array.from(document.querySelectorAll('[data-admin-nav-section]'));
16
- let isFilteringNavigation = false;
16
+ const programmaticSectionToggles = new WeakSet();
17
+ let filterBaselineOpenSections = null;
18
+ let hadFilterQuery = false;
17
19
 
18
20
  function setCollapsed(isCollapsed, shouldPersist) {
19
21
  body.classList.toggle('admin-sidebar-collapsed', isCollapsed);
@@ -60,6 +62,10 @@
60
62
  return section.dataset.adminNavSection;
61
63
  }
62
64
 
65
+ function isCurrentSection(section) {
66
+ return section.classList.contains('is-current-app');
67
+ }
68
+
63
69
  function syncSectionAria(section) {
64
70
  const summary = section.querySelector('.admin-sidebar__group-title');
65
71
  if (summary) {
@@ -67,6 +73,13 @@
67
73
  }
68
74
  }
69
75
 
76
+ function getCurrentOpenSections() {
77
+ return new Set(navSections
78
+ .filter(isSectionOpen)
79
+ .map(getSectionKey)
80
+ .filter(Boolean));
81
+ }
82
+
70
83
  function getStoredOpenSections() {
71
84
  try {
72
85
  const value = JSON.parse(localStorage.getItem(navSectionsStateKey));
@@ -77,16 +90,40 @@
77
90
  }
78
91
 
79
92
  function persistOpenSections() {
80
- if (isFilteringNavigation) {
93
+ const openSections = Array.from(getCurrentOpenSections());
94
+
95
+ localStorage.setItem(navSectionsStateKey, JSON.stringify(openSections));
96
+ }
97
+
98
+ function persistFilteredSectionToggle(section) {
99
+ const sectionKey = getSectionKey(section);
100
+ if (!sectionKey) {
81
101
  return;
82
102
  }
83
103
 
84
- const openSections = navSections
85
- .filter(isSectionOpen)
86
- .map(getSectionKey)
87
- .filter(Boolean);
104
+ const openSections = new Set(filterBaselineOpenSections || getStoredOpenSections() || []);
88
105
 
89
- localStorage.setItem(navSectionsStateKey, JSON.stringify(openSections));
106
+ if (isSectionOpen(section)) {
107
+ openSections.add(sectionKey);
108
+ } else {
109
+ openSections.delete(sectionKey);
110
+ }
111
+
112
+ filterBaselineOpenSections = openSections;
113
+ localStorage.setItem(navSectionsStateKey, JSON.stringify(Array.from(openSections)));
114
+ }
115
+
116
+ function setSectionOpen(section, isOpen, shouldPersist) {
117
+ if (section.open === isOpen) {
118
+ return;
119
+ }
120
+
121
+ if (!shouldPersist) {
122
+ programmaticSectionToggles.add(section);
123
+ }
124
+
125
+ section.open = isOpen;
126
+ syncSectionAria(section);
90
127
  }
91
128
 
92
129
  function normalizeFilterValue(value) {
@@ -105,7 +142,9 @@
105
142
  const query = normalizeFilterValue(navFilter.value);
106
143
  const hasQuery = query.length > 0;
107
144
 
108
- isFilteringNavigation = true;
145
+ if (hasQuery && !hadFilterQuery) {
146
+ filterBaselineOpenSections = getCurrentOpenSections();
147
+ }
109
148
 
110
149
  navSections.forEach((section) => {
111
150
  const sectionTitle = section.querySelector('.admin-sidebar__group-title span');
@@ -124,46 +163,59 @@
124
163
  section.hidden = hasQuery && !matchesSection && !hasVisibleModel;
125
164
 
126
165
  if (hasQuery && !section.hidden) {
127
- section.open = true;
166
+ setSectionOpen(section, true, false);
128
167
  }
129
168
 
130
169
  syncSectionAria(section);
131
170
  });
132
171
 
133
172
  if (!hasQuery) {
134
- applyStoredOpenSections();
173
+ applyOpenSections(filterBaselineOpenSections || getStoredOpenSections());
135
174
  navSections.forEach((section) => {
136
175
  section.hidden = false;
137
176
  section.querySelectorAll('.admin-sidebar__model').forEach((item) => {
138
177
  item.hidden = false;
139
178
  });
140
179
  });
180
+ filterBaselineOpenSections = null;
141
181
  }
142
182
 
143
- isFilteringNavigation = false;
183
+ hadFilterQuery = hasQuery;
144
184
  }
145
185
 
146
- function applyStoredOpenSections() {
147
- const storedOpenSections = getStoredOpenSections();
148
-
149
- if (!storedOpenSections) {
186
+ function applyOpenSections(openSections, shouldPersist) {
187
+ if (!openSections) {
150
188
  root.classList.remove('admin-nav-state-pending');
151
189
  return;
152
190
  }
153
191
 
192
+ const nextOpenSections = new Set(openSections);
193
+ navSections
194
+ .filter(isCurrentSection)
195
+ .map(getSectionKey)
196
+ .filter(Boolean)
197
+ .forEach((sectionKey) => nextOpenSections.add(sectionKey));
198
+
154
199
  navSections.forEach((section) => {
155
200
  const sectionKey = getSectionKey(section);
156
201
  if (!sectionKey) {
157
202
  return;
158
203
  }
159
204
 
160
- section.open = storedOpenSections.has(sectionKey);
161
- syncSectionAria(section);
205
+ setSectionOpen(section, nextOpenSections.has(sectionKey), false);
162
206
  });
163
207
 
208
+ if (shouldPersist) {
209
+ localStorage.setItem(navSectionsStateKey, JSON.stringify(Array.from(nextOpenSections)));
210
+ }
211
+
164
212
  root.classList.remove('admin-nav-state-pending');
165
213
  }
166
214
 
215
+ function applyStoredOpenSections() {
216
+ applyOpenSections(getStoredOpenSections(), true);
217
+ }
218
+
167
219
  const storedCollapsed = localStorage.getItem(sidebarStateKey);
168
220
  setCollapsed(storedCollapsed === 'true', false);
169
221
  setMobileOpen(false);
@@ -190,6 +242,16 @@
190
242
  syncSectionAria(section);
191
243
  section.addEventListener('toggle', () => {
192
244
  syncSectionAria(section);
245
+ if (programmaticSectionToggles.has(section)) {
246
+ programmaticSectionToggles.delete(section);
247
+ return;
248
+ }
249
+
250
+ if (hadFilterQuery) {
251
+ persistFilteredSectionToggle(section);
252
+ return;
253
+ }
254
+
193
255
  persistOpenSections();
194
256
  });
195
257
  });
@@ -2,7 +2,7 @@
2
2
  <div class="breadcrumbs">
3
3
  <a href="{% url 'admin:index' %}">{% translate 'Home' %}</a>
4
4
  <span class="admin-breadcrumb-separator" aria-hidden="true">&rsaquo;</span>
5
- <a href="{% url 'admin:app_list' app_label=opts.app_label %}">{{ opts.app_config.verbose_name }}</a>
5
+ <span class="admin-breadcrumb-app">{{ opts.app_config.verbose_name }}</span>
6
6
  <span class="admin-breadcrumb-separator" aria-hidden="true">&rsaquo;</span>
7
7
  {% if show_model_link %}
8
8
  <a href="{% url opts|admin_urlname:'changelist' %}">{{ opts.verbose_name_plural|capfirst }}</a>
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vanta-admin
3
- Version: 0.5.0
3
+ Version: 0.6.0
4
4
  Summary: A sharp, dark, and portable Django admin theme forged for builders who want the admin to feel less default.
5
5
  Author: oli-dev0
6
6
  License-Expression: MIT
@@ -42,6 +42,10 @@ feels more deliberate in everyday use.
42
42
  two-factor auth pages.
43
43
  - Denser dashboards, tables, filters, messages, and forms so admin work feels
44
44
  easier to scan.
45
+ - Better edit pages for longer admin forms, with save buttons that stay easier
46
+ to reach while scrolling.
47
+ - Cleaner buttons for saving, deleting, viewing history, and changing passwords.
48
+ - Clearer form errors on admin pages, login, and two-factor auth screens.
45
49
  - Admin messages that appear clearly, pause while hovered, and dismiss
46
50
  themselves after a short time.
47
51
  - Changelist actions that appear only when rows are selected, stay available
@@ -55,6 +59,7 @@ feels more deliberate in everyday use.
55
59
  stored in the browser for each admin user.
56
60
  - Shorter changelist date formatting that makes date-heavy tables easier to
57
61
  scan.
62
+ - Simpler breadcrumbs on edit pages, so the page path feels less cluttered.
58
63
  - A packaged favicon for the admin.
59
64
  - A mobile sidebar overlay that makes the admin easier to use on smaller
60
65
  screens.
File without changes
File without changes
File without changes