vanta-admin 0.1.7__tar.gz → 0.2.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 (40) hide show
  1. {vanta_admin-0.1.7/vanta_admin.egg-info → vanta_admin-0.2.0}/PKG-INFO +21 -15
  2. {vanta_admin-0.1.7 → vanta_admin-0.2.0}/README.md +20 -14
  3. {vanta_admin-0.1.7 → vanta_admin-0.2.0}/pyproject.toml +1 -1
  4. {vanta_admin-0.1.7 → vanta_admin-0.2.0}/vanta_admin/static/admin/css/theme.admin.css +373 -25
  5. {vanta_admin-0.1.7 → vanta_admin-0.2.0}/vanta_admin/static/admin/css/theme.base.css +1 -1
  6. {vanta_admin-0.1.7 → vanta_admin-0.2.0}/vanta_admin/static/admin/css/theme.responsive.css +6 -0
  7. {vanta_admin-0.1.7 → vanta_admin-0.2.0}/vanta_admin/static/admin/js/sidebar.js +122 -3
  8. {vanta_admin-0.1.7 → vanta_admin-0.2.0}/vanta_admin/templates/admin/nav_sidebar.html +19 -1
  9. {vanta_admin-0.1.7 → vanta_admin-0.2.0/vanta_admin.egg-info}/PKG-INFO +21 -15
  10. {vanta_admin-0.1.7 → vanta_admin-0.2.0}/LICENSE +0 -0
  11. {vanta_admin-0.1.7 → vanta_admin-0.2.0}/MANIFEST.in +0 -0
  12. {vanta_admin-0.1.7 → vanta_admin-0.2.0}/setup.cfg +0 -0
  13. {vanta_admin-0.1.7 → vanta_admin-0.2.0}/vanta_admin/__init__.py +0 -0
  14. {vanta_admin-0.1.7 → vanta_admin-0.2.0}/vanta_admin/apps.py +0 -0
  15. {vanta_admin-0.1.7 → vanta_admin-0.2.0}/vanta_admin/static/admin/css/theme.css +0 -0
  16. {vanta_admin-0.1.7 → vanta_admin-0.2.0}/vanta_admin/static/admin/css/theme.login.css +0 -0
  17. {vanta_admin-0.1.7 → vanta_admin-0.2.0}/vanta_admin/static/admin/css/theme.tokens.css +0 -0
  18. {vanta_admin-0.1.7 → vanta_admin-0.2.0}/vanta_admin/static/admin/css/two_factor.css +0 -0
  19. {vanta_admin-0.1.7 → vanta_admin-0.2.0}/vanta_admin/static/admin/img/favicon.png +0 -0
  20. {vanta_admin-0.1.7 → vanta_admin-0.2.0}/vanta_admin/templates/admin/app_index.html +0 -0
  21. {vanta_admin-0.1.7 → vanta_admin-0.2.0}/vanta_admin/templates/admin/base.html +0 -0
  22. {vanta_admin-0.1.7 → vanta_admin-0.2.0}/vanta_admin/templates/admin/base_site.html +0 -0
  23. {vanta_admin-0.1.7 → vanta_admin-0.2.0}/vanta_admin/templates/admin/change_form.html +0 -0
  24. {vanta_admin-0.1.7 → vanta_admin-0.2.0}/vanta_admin/templates/admin/change_list.html +0 -0
  25. {vanta_admin-0.1.7 → vanta_admin-0.2.0}/vanta_admin/templates/admin/filter.html +0 -0
  26. {vanta_admin-0.1.7 → vanta_admin-0.2.0}/vanta_admin/templates/admin/includes/breadcrumbs.html +0 -0
  27. {vanta_admin-0.1.7 → vanta_admin-0.2.0}/vanta_admin/templates/admin/index.html +0 -0
  28. {vanta_admin-0.1.7 → vanta_admin-0.2.0}/vanta_admin/templates/admin/login.html +0 -0
  29. {vanta_admin-0.1.7 → vanta_admin-0.2.0}/vanta_admin/templates/two_factor/_base.html +0 -0
  30. {vanta_admin-0.1.7 → vanta_admin-0.2.0}/vanta_admin/templates/two_factor/_base_focus.html +0 -0
  31. {vanta_admin-0.1.7 → vanta_admin-0.2.0}/vanta_admin/templates/two_factor/_wizard_actions.html +0 -0
  32. {vanta_admin-0.1.7 → vanta_admin-0.2.0}/vanta_admin/templates/two_factor/core/login.html +0 -0
  33. {vanta_admin-0.1.7 → vanta_admin-0.2.0}/vanta_admin/templates/two_factor/core/setup_complete.html +0 -0
  34. {vanta_admin-0.1.7 → vanta_admin-0.2.0}/vanta_admin/templates/two_factor/profile/profile.html +0 -0
  35. {vanta_admin-0.1.7 → vanta_admin-0.2.0}/vanta_admin/templatetags/__init__.py +0 -0
  36. {vanta_admin-0.1.7 → vanta_admin-0.2.0}/vanta_admin/templatetags/admin_theme.py +0 -0
  37. {vanta_admin-0.1.7 → vanta_admin-0.2.0}/vanta_admin.egg-info/SOURCES.txt +0 -0
  38. {vanta_admin-0.1.7 → vanta_admin-0.2.0}/vanta_admin.egg-info/dependency_links.txt +0 -0
  39. {vanta_admin-0.1.7 → vanta_admin-0.2.0}/vanta_admin.egg-info/requires.txt +0 -0
  40. {vanta_admin-0.1.7 → vanta_admin-0.2.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.1.7
3
+ Version: 0.2.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
@@ -32,20 +32,26 @@ standard Django admin behavior.
32
32
 
33
33
  ## What It Adds
34
34
 
35
- - A fixed sidebar with persistent collapsed state and remembered open sections.
36
- - Light and dark theme support through CSS tokens.
37
- - A flatter visual pass that removes heavy shadows and blur effects from the
38
- admin shell, login screens, cards, filters, and two-factor auth views.
39
- - A denser dashboard, changelist, filter, message, and form presentation.
40
- - Auto-dismissing admin messages with success, warning, and error treatment.
41
- - Custom single-select controls for changelist actions and normal admin form
42
- selects, while native multi-selects stay unchanged.
43
- - Default icons for common admin apps, models, and recent actions, including
44
- incident and uptime-monitor style models.
45
- - A packaged admin favicon loaded through the theme.
46
- - A responsive mobile sidebar overlay.
47
- - Optional themed templates for `django-two-factor-auth` login, setup completion,
48
- account security, and backup-token flows.
35
+ - A cleaner admin layout with a fixed sidebar that remembers how you left it.
36
+ - Light and dark mode, using the same calm Vanta look across the admin.
37
+ - Less visual noise on the main admin screens, login pages, cards, filters, and
38
+ two-factor auth pages.
39
+ - Denser dashboards, tables, filters, messages, and forms so admin work feels
40
+ easier to scan.
41
+ - Admin messages that appear clearly, pause while hovered, and dismiss
42
+ themselves after a short time.
43
+ - Changelist actions that appear only when rows are selected, stay available
44
+ while scrolling, and include a clear-selected control.
45
+ - Styled select controls for common admin dropdowns, while Django's native
46
+ multi-select fields stay unchanged.
47
+ - Built-in icons for common admin areas, models, and recent actions.
48
+ - A cleaner account menu with theme controls, project links, password access,
49
+ logout, and a Vanta support link.
50
+ - A packaged favicon for the admin.
51
+ - A mobile sidebar overlay that makes the admin easier to use on smaller
52
+ screens.
53
+ - Optional matching templates for `django-two-factor-auth` login, setup
54
+ completion, account security, and backup-token screens.
49
55
 
50
56
  Vanta Admin does not replace Django admin, change your permissions, or configure
51
57
  two-factor authentication for you. If your project uses `django-two-factor-auth`,
@@ -7,20 +7,26 @@ standard Django admin behavior.
7
7
 
8
8
  ## What It Adds
9
9
 
10
- - A fixed sidebar with persistent collapsed state and remembered open sections.
11
- - Light and dark theme support through CSS tokens.
12
- - A flatter visual pass that removes heavy shadows and blur effects from the
13
- admin shell, login screens, cards, filters, and two-factor auth views.
14
- - A denser dashboard, changelist, filter, message, and form presentation.
15
- - Auto-dismissing admin messages with success, warning, and error treatment.
16
- - Custom single-select controls for changelist actions and normal admin form
17
- selects, while native multi-selects stay unchanged.
18
- - Default icons for common admin apps, models, and recent actions, including
19
- incident and uptime-monitor style models.
20
- - A packaged admin favicon loaded through the theme.
21
- - A responsive mobile sidebar overlay.
22
- - Optional themed templates for `django-two-factor-auth` login, setup completion,
23
- account security, and backup-token flows.
10
+ - A cleaner admin layout with a fixed sidebar that remembers how you left it.
11
+ - Light and dark mode, using the same calm Vanta look across the admin.
12
+ - Less visual noise on the main admin screens, login pages, cards, filters, and
13
+ two-factor auth pages.
14
+ - Denser dashboards, tables, filters, messages, and forms so admin work feels
15
+ easier to scan.
16
+ - Admin messages that appear clearly, pause while hovered, and dismiss
17
+ themselves after a short time.
18
+ - Changelist actions that appear only when rows are selected, stay available
19
+ while scrolling, and include a clear-selected control.
20
+ - Styled select controls for common admin dropdowns, while Django's native
21
+ multi-select fields stay unchanged.
22
+ - Built-in icons for common admin areas, models, and recent actions.
23
+ - A cleaner account menu with theme controls, project links, password access,
24
+ logout, and a Vanta support link.
25
+ - A packaged favicon for the admin.
26
+ - A mobile sidebar overlay that makes the admin easier to use on smaller
27
+ screens.
28
+ - Optional matching templates for `django-two-factor-auth` login, setup
29
+ completion, account security, and backup-token screens.
24
30
 
25
31
  Vanta Admin does not replace Django admin, change your permissions, or configure
26
32
  two-factor authentication for you. If your project uses `django-two-factor-auth`,
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "vanta-admin"
7
- version = "0.1.7"
7
+ version = "0.2.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"
@@ -283,23 +283,15 @@ body.admin-sidebar-collapsed .admin-sidebar__collapse-arrow {
283
283
  list-style: none;
284
284
  }
285
285
 
286
- .admin-sidebar__avatar {
287
- display: inline-grid;
288
- place-items: center;
289
- width: 2.6rem;
290
- min-width: 2.6rem;
291
- height: 2.6rem;
292
- border: 1px solid color-mix(in srgb, var(--accent) 34%, transparent);
293
- border-radius: 0.36rem;
294
- background: var(--admin-accent-soft);
295
- color: var(--admin-accent-strong);
296
- font-weight: 900;
297
- letter-spacing: 0;
298
- }
299
-
300
- html[data-theme="light"] .admin-sidebar__avatar {
301
- background: var(--admin-accent-soft);
302
- color: var(--admin-accent);
286
+ .admin-sidebar__account-icon {
287
+ width: 1.15rem;
288
+ height: 1.15rem;
289
+ color: color-mix(in srgb, var(--muted) 82%, var(--accent));
290
+ fill: none;
291
+ stroke: currentColor;
292
+ stroke-linecap: round;
293
+ stroke-linejoin: round;
294
+ stroke-width: 32px;
303
295
  }
304
296
 
305
297
  .admin-sidebar__account-copy {
@@ -321,6 +313,13 @@ html[data-theme="light"] .admin-sidebar__avatar {
321
313
  color: var(--heading);
322
314
  font-size: 0.92rem;
323
315
  font-weight: 760;
316
+ transition: color var(--admin-transition), transform var(--admin-transition);
317
+ }
318
+
319
+ .admin-sidebar__account-summary:hover .admin-sidebar__username,
320
+ .admin-sidebar__account-summary:focus-visible .admin-sidebar__username {
321
+ color: var(--accent-strong);
322
+ transform: translateY(-2px);
324
323
  }
325
324
 
326
325
  .admin-sidebar__group-name {
@@ -416,6 +415,80 @@ html[data-theme="light"] .admin-sidebar__theme-button--light {
416
415
  color: var(--muted);
417
416
  }
418
417
 
418
+ .admin-sidebar__account-links .admin-sidebar__donation-link {
419
+ color: var(--accent-strong);
420
+ }
421
+
422
+ .admin-sidebar__donation-link .donation-button__label {
423
+ min-width: 0;
424
+ }
425
+
426
+ .admin-sidebar__donation-link .donation-button__cup {
427
+ position: relative;
428
+ width: 0.92rem;
429
+ height: 0.72rem;
430
+ border: 2px solid currentColor;
431
+ border-radius: 0 0 0.28rem 0.28rem;
432
+ }
433
+
434
+ .admin-sidebar__donation-link .donation-button__cup::before {
435
+ content: "";
436
+ position: absolute;
437
+ top: 0.1rem;
438
+ right: -0.38rem;
439
+ width: 0.3rem;
440
+ height: 0.3rem;
441
+ border: 2px solid currentColor;
442
+ border-left: 0;
443
+ border-radius: 0 0.35rem 0.35rem 0;
444
+ }
445
+
446
+ .admin-sidebar__donation-link .donation-button__cup::after {
447
+ content: "";
448
+ position: absolute;
449
+ right: -0.08rem;
450
+ bottom: -0.25rem;
451
+ left: -0.08rem;
452
+ height: 2px;
453
+ border-radius: 999px;
454
+ background: currentColor;
455
+ opacity: 0.75;
456
+ }
457
+
458
+ .admin-sidebar__donation-link .donation-button__steam,
459
+ .admin-sidebar__donation-link .donation-button__steam::before,
460
+ .admin-sidebar__donation-link .donation-button__steam::after {
461
+ content: "";
462
+ position: absolute;
463
+ bottom: 0.78rem;
464
+ width: 2px;
465
+ height: 0.38rem;
466
+ border-radius: 999px;
467
+ background: currentColor;
468
+ opacity: 0.65;
469
+ animation: donation-steam 1.8s ease-in-out infinite;
470
+ }
471
+
472
+ .admin-sidebar__donation-link .donation-button__steam {
473
+ left: 0.4rem;
474
+ }
475
+
476
+ .admin-sidebar__donation-link .donation-button__steam::before {
477
+ left: -0.25rem;
478
+ animation-delay: 0.25s;
479
+ }
480
+
481
+ .admin-sidebar__donation-link .donation-button__steam::after {
482
+ left: 0.25rem;
483
+ animation-delay: 0.5s;
484
+ }
485
+
486
+ .admin-sidebar__account-links .admin-sidebar__external-icon {
487
+ width: 0.86rem;
488
+ height: 0.86rem;
489
+ margin-left: auto;
490
+ }
491
+
419
492
  .admin-sidebar__account-links a:hover,
420
493
  .admin-sidebar__account-links a:focus,
421
494
  .admin-sidebar__account-links button:hover,
@@ -439,6 +512,19 @@ html[data-theme="light"] .admin-sidebar__theme-button--light {
439
512
  background: var(--border);
440
513
  }
441
514
 
515
+ @keyframes donation-steam {
516
+ 0%,
517
+ 100% {
518
+ transform: translateY(0) scaleY(0.72);
519
+ opacity: 0.35;
520
+ }
521
+
522
+ 50% {
523
+ transform: translateY(-0.16rem) scaleY(1);
524
+ opacity: 0.9;
525
+ }
526
+ }
527
+
442
528
  body.admin-sidebar-collapsed .main {
443
529
  grid-template-columns: var(--admin-sidebar-collapsed-width) minmax(0, 1fr);
444
530
  }
@@ -729,6 +815,10 @@ body.change-list #content-main {
729
815
  max-width: 100%;
730
816
  }
731
817
 
818
+ #changelist-form {
819
+ position: relative;
820
+ }
821
+
732
822
  .admin-changelist-card-header {
733
823
  display: flex;
734
824
  gap: 0.85rem;
@@ -762,7 +852,7 @@ fieldset.module,
762
852
  }
763
853
 
764
854
  .submit-row {
765
- background: var(--admin-surface-raised);
855
+ background: var(--accent-soft);
766
856
  }
767
857
 
768
858
  #changelist #toolbar,
@@ -778,6 +868,91 @@ fieldset.module,
778
868
  box-shadow: none;
779
869
  }
780
870
 
871
+ #changelist .actions {
872
+ position: absolute;
873
+ top: 0.18rem;
874
+ right: 0.72rem;
875
+ left: 0.72rem;
876
+ z-index: 8;
877
+ max-width: none;
878
+ padding-left: 0.87rem;
879
+ border: 1px solid var(--border-strong);
880
+ border-radius: var(--radius-md);
881
+ background: #151719;
882
+ opacity: 0;
883
+ pointer-events: none;
884
+ visibility: hidden;
885
+ transform: translateY(-0.18rem);
886
+ transition:
887
+ opacity var(--admin-transition),
888
+ transform var(--admin-transition),
889
+ visibility var(--admin-transition);
890
+ }
891
+
892
+ html[data-theme="light"] #changelist .actions {
893
+ background: #e7f0ff;
894
+ }
895
+
896
+ #changelist-form.has-selected-actions .actions,
897
+ #changelist-form:has(input.action-select[type="checkbox"]:checked) .actions {
898
+ opacity: 1;
899
+ pointer-events: auto;
900
+ visibility: visible;
901
+ transform: translateY(0);
902
+ }
903
+
904
+ #changelist-form.has-sticky-actions .actions {
905
+ position: fixed;
906
+ top: 0.75rem;
907
+ right: auto;
908
+ left: var(--admin-actions-fixed-left, 0.72rem);
909
+ width: var(--admin-actions-fixed-width, auto);
910
+ z-index: 30;
911
+ }
912
+
913
+ #changelist .actions.admin-actions--duplicate {
914
+ display: none;
915
+ }
916
+
917
+ #changelist .actions .admin-actions__clear {
918
+ position: relative;
919
+ display: inline-grid;
920
+ place-items: center;
921
+ flex: 0 0 1.5rem;
922
+ width: 1.5rem;
923
+ min-width: 1.5rem;
924
+ min-height: 1.5rem;
925
+ padding: 0;
926
+ border: 1px solid color-mix(in srgb, var(--admin-error) 72%, var(--border-strong));
927
+ border-radius: var(--radius-sm);
928
+ background: color-mix(in srgb, var(--admin-error) 76%, var(--surface-muted));
929
+ color: var(--admin-error-text);
930
+ }
931
+
932
+ #changelist .actions .admin-actions__clear::before,
933
+ #changelist .actions .admin-actions__clear::after {
934
+ content: "";
935
+ position: absolute;
936
+ width: 0.62rem;
937
+ height: 2px;
938
+ border-radius: 999px;
939
+ background: currentColor;
940
+ }
941
+
942
+ #changelist .actions .admin-actions__clear::before {
943
+ transform: rotate(45deg);
944
+ }
945
+
946
+ #changelist .actions .admin-actions__clear::after {
947
+ transform: rotate(-45deg);
948
+ }
949
+
950
+ #changelist .actions .admin-actions__clear:hover,
951
+ #changelist .actions .admin-actions__clear:focus-visible {
952
+ color: var(--admin-error-text);
953
+ background: var(--admin-error);
954
+ }
955
+
781
956
  #changelist .admin-changelist-card-header #changelist-search > div {
782
957
  display: flex;
783
958
  align-items: center;
@@ -1254,6 +1429,7 @@ input[type="checkbox"]:focus-visible {
1254
1429
  gap: 0.2rem;
1255
1430
  height: 1.8rem;
1256
1431
  min-height: 1.8rem;
1432
+ margin-top: 2px;
1257
1433
  padding: 0 0.42rem;
1258
1434
  border: 0;
1259
1435
  border-radius: 0.27rem;
@@ -1270,6 +1446,11 @@ input[type="checkbox"]:focus-visible {
1270
1446
  text-transform: none;
1271
1447
  white-space: nowrap;
1272
1448
  overflow: hidden;
1449
+ transition:
1450
+ background-color var(--admin-transition),
1451
+ border-color var(--admin-transition),
1452
+ color var(--admin-transition),
1453
+ transform var(--admin-transition);
1273
1454
  }
1274
1455
 
1275
1456
  #changelist .object-tools a.addlink::before {
@@ -1296,6 +1477,13 @@ input[type="checkbox"]:focus-visible {
1296
1477
  box-shadow: none;
1297
1478
  }
1298
1479
 
1480
+ #changelist .object-tools a:hover,
1481
+ #changelist .object-tools a:focus-visible,
1482
+ #changelist .object-tools a.addlink:hover,
1483
+ #changelist .object-tools a.addlink:focus-visible {
1484
+ transform: translateY(-2px);
1485
+ }
1486
+
1299
1487
  #changelist input[type="submit"]:active,
1300
1488
  #changelist button:active,
1301
1489
  .object-tools a:active,
@@ -1319,10 +1507,10 @@ input[type="checkbox"]:focus-visible {
1319
1507
 
1320
1508
  #changelist table thead th {
1321
1509
  border: 0;
1322
- background: transparent;
1510
+ background: color-mix(in srgb, var(--admin-bg) 72%, var(--admin-surface-muted));
1323
1511
  color: var(--text);
1324
1512
  font-size: 0.74rem;
1325
- font-weight: 560;
1513
+ font-weight: 760;
1326
1514
  letter-spacing: 0;
1327
1515
  height: auto;
1328
1516
  padding: 0.05rem 0.56rem;
@@ -1331,20 +1519,180 @@ input[type="checkbox"]:focus-visible {
1331
1519
  vertical-align: middle;
1332
1520
  }
1333
1521
 
1522
+ #changelist table thead th:first-child {
1523
+ border-radius: var(--admin-radius-sm) 0 0 var(--admin-radius-sm);
1524
+ }
1525
+
1526
+ #changelist table thead th:last-child {
1527
+ border-radius: 0 var(--admin-radius-sm) var(--admin-radius-sm) 0;
1528
+ }
1529
+
1334
1530
  #changelist table thead th.sorted {
1335
- border: 1px solid var(--admin-accent-border);
1336
- border-radius: var(--admin-radius-sm);
1337
- background: var(--admin-nav-item-active-bg);
1338
- color: var(--admin-accent-strong);
1531
+ direction: rtl;
1532
+ font-size: 0;
1339
1533
  font-weight: 720;
1534
+ text-align: left;
1535
+ white-space: nowrap;
1536
+ }
1537
+
1538
+ #changelist table thead th .text {
1539
+ display: inline-flex;
1540
+ align-items: center;
1340
1541
  }
1341
1542
 
1342
1543
  #changelist table thead th a {
1544
+ display: inline-flex;
1545
+ align-items: center;
1546
+ border-radius: var(--admin-radius-sm);
1547
+ background: transparent;
1548
+ border: 0;
1549
+ box-shadow: none;
1343
1550
  color: inherit;
1551
+ padding: 0.16rem 0.42rem;
1552
+ transition:
1553
+ background-color var(--admin-transition),
1554
+ color var(--admin-transition),
1555
+ transform var(--admin-transition);
1556
+ }
1557
+
1558
+ #changelist table thead th a:hover,
1559
+ #changelist table thead th a:focus {
1560
+ background: var(--admin-nav-item-active-bg);
1561
+ box-shadow: none;
1562
+ color: var(--admin-accent-strong);
1563
+ transform: translateY(-2px);
1564
+ }
1565
+
1566
+ #changelist table thead th a:focus-visible {
1567
+ outline: 2px solid var(--admin-accent);
1568
+ outline-offset: 2px;
1569
+ }
1570
+
1571
+ #changelist table thead th.sorted .text,
1572
+ #changelist table thead th.sorted .sortoptions {
1573
+ display: inline-flex;
1574
+ align-items: center;
1575
+ min-height: 1.32rem;
1576
+ direction: ltr;
1577
+ font-size: 0.74rem;
1578
+ margin: 0 !important;
1579
+ background: var(--admin-nav-item-active-bg) !important;
1580
+ color: var(--admin-accent-strong);
1581
+ vertical-align: middle;
1582
+ }
1583
+
1584
+ #changelist table thead th.sorted .text {
1585
+ float: none !important;
1586
+ padding: 0 !important;
1587
+ border-radius: var(--admin-radius-sm) 0 0 var(--admin-radius-sm);
1588
+ }
1589
+
1590
+ #changelist table thead th.sorted .text a {
1591
+ background: transparent !important;
1592
+ min-height: 1.32rem;
1593
+ padding-right: 0.18rem;
1594
+ color: var(--admin-accent-strong);
1595
+ }
1596
+
1597
+ #changelist table thead th.sorted .text a:hover,
1598
+ #changelist table thead th.sorted .text a:focus {
1599
+ background: transparent !important;
1600
+ transform: none;
1601
+ }
1602
+
1603
+ #changelist table thead th.sorted .sortoptions {
1604
+ float: none !important;
1605
+ gap: 0.03rem;
1606
+ margin-left: 0 !important;
1607
+ padding: 0.16rem 0.28rem 0.16rem 0.16rem;
1608
+ border-radius: 0 var(--admin-radius-sm) var(--admin-radius-sm) 0;
1609
+ }
1610
+
1611
+ #changelist table thead th.sorted .sortoptions a {
1612
+ background: transparent !important;
1613
+ }
1614
+
1615
+ #changelist table thead th.sorted .sortremove,
1616
+ #changelist table thead th.sorted .toggle {
1617
+ display: inline-grid;
1618
+ place-items: center;
1619
+ width: 0.72rem;
1620
+ height: 0.92rem;
1621
+ padding: 0;
1622
+ background: transparent !important;
1623
+ background-image: none !important;
1624
+ color: var(--admin-accent-strong);
1625
+ font-size: 0;
1626
+ line-height: 1;
1627
+ overflow: hidden;
1628
+ text-indent: 0;
1629
+ transform: none;
1630
+ visibility: visible !important;
1631
+ }
1632
+
1633
+ #changelist table thead th.sorted .toggle {
1634
+ order: 1;
1635
+ }
1636
+
1637
+ #changelist table thead th.sorted .sortpriority {
1638
+ order: 2;
1639
+ }
1640
+
1641
+ #changelist table thead th.sorted .sortremove {
1642
+ order: 3;
1643
+ }
1644
+
1645
+ #changelist table thead th.sorted .sortremove::before,
1646
+ #changelist table thead th.sorted .toggle::before {
1647
+ font-size: 0.72rem;
1648
+ font-weight: 860;
1649
+ line-height: 1;
1650
+ }
1651
+
1652
+ #changelist table thead th.sorted .sortremove::before {
1653
+ content: "X";
1654
+ color: var(--admin-accent-strong);
1655
+ font-size: 0.76rem;
1656
+ font-weight: 920;
1657
+ opacity: 1;
1658
+ }
1659
+
1660
+ #changelist table thead th.sorted .sortremove::after {
1661
+ content: none !important;
1662
+ display: none !important;
1344
1663
  }
1345
1664
 
1346
- #changelist table thead th.sorted a {
1665
+ #changelist table thead th.sorted .toggle.ascending::before {
1666
+ content: "🡱";
1667
+ font-size: 0.76rem;
1668
+ }
1669
+
1670
+ #changelist table thead th.sorted .toggle.descending::before {
1671
+ content: "🡳";
1672
+ font-size: 0.76rem;
1673
+ }
1674
+
1675
+ #changelist table thead th.sorted .sortpriority {
1676
+ display: inline-grid;
1677
+ place-items: center;
1678
+ min-width: 0.72rem;
1679
+ height: 0.92rem;
1680
+ margin: 0 !important;
1681
+ padding: 0 0.08rem;
1682
+ background: transparent !important;
1683
+ color: var(--admin-accent-strong);
1684
+ font-size: 0.64rem;
1685
+ font-weight: 860;
1686
+ line-height: 1;
1687
+ }
1688
+
1689
+ #changelist table thead th.sorted .sortremove:hover,
1690
+ #changelist table thead th.sorted .sortremove:focus,
1691
+ #changelist table thead th.sorted .toggle:hover,
1692
+ #changelist table thead th.sorted .toggle:focus {
1693
+ background: transparent !important;
1347
1694
  color: var(--admin-accent-strong);
1695
+ transform: none;
1348
1696
  }
1349
1697
 
1350
1698
  #changelist table tbody th,
@@ -146,7 +146,7 @@ ul.messagelist li {
146
146
  line-height: 1.3;
147
147
  overflow-wrap: anywhere;
148
148
  transition:
149
- background-size 5000ms linear,
149
+ background-size 4000ms linear,
150
150
  opacity 160ms ease,
151
151
  transform 160ms ease;
152
152
  }
@@ -262,6 +262,12 @@
262
262
  .admin-action-link {
263
263
  transition: none;
264
264
  }
265
+
266
+ .admin-sidebar__donation-link .donation-button__steam,
267
+ .admin-sidebar__donation-link .donation-button__steam::before,
268
+ .admin-sidebar__donation-link .donation-button__steam::after {
269
+ animation: none;
270
+ }
265
271
  }
266
272
 
267
273
  /* Links are clean by default; only text-content links opt into hover underlines. */
@@ -17,6 +17,7 @@
17
17
  const navSections = Array.from(document.querySelectorAll('[data-admin-nav-section]'));
18
18
  const filterSections = Array.from(document.querySelectorAll('#changelist-filter details'));
19
19
  const adminMessages = Array.from(document.querySelectorAll('ul.messagelist li'));
20
+ const adminMessageDuration = 4000;
20
21
  const customSelects = Array.from(document.querySelectorAll('select')).filter((select) => (
21
22
  !select.multiple
22
23
  && (!select.size || select.size <= 1)
@@ -136,6 +137,8 @@
136
137
  }
137
138
 
138
139
  function dismissAdminMessage(message) {
140
+ window.clearTimeout(message.adminDismissTimer);
141
+ message.adminDismissTimer = null;
139
142
  message.classList.add('is-removing');
140
143
  window.setTimeout(() => {
141
144
  const messageList = message.closest('ul.messagelist');
@@ -146,24 +149,139 @@
146
149
  }, 180);
147
150
  }
148
151
 
152
+ function setAdminMessageTimer(message, duration) {
153
+ window.clearTimeout(message.adminDismissTimer);
154
+ message.adminTimerStartedAt = Date.now();
155
+ message.adminTimerRemaining = duration;
156
+ message.style.transition = `background-size ${duration}ms linear, opacity 160ms ease, transform 160ms ease`;
157
+ message.style.backgroundSize = '100% 100%';
158
+ message.adminDismissTimer = window.setTimeout(() => dismissAdminMessage(message), duration);
159
+ }
160
+
161
+ function pauseAdminMessageTimer(message) {
162
+ if (!message.adminDismissTimer || message.classList.contains('is-removing')) {
163
+ return;
164
+ }
165
+
166
+ const elapsed = Date.now() - message.adminTimerStartedAt;
167
+ message.adminTimerRemaining = Math.max(0, message.adminTimerRemaining - elapsed);
168
+ window.clearTimeout(message.adminDismissTimer);
169
+ message.adminDismissTimer = null;
170
+ message.style.backgroundSize = window.getComputedStyle(message).backgroundSize;
171
+ message.style.transition = 'opacity 160ms ease, transform 160ms ease';
172
+ }
173
+
174
+ function resumeAdminMessageTimer(message) {
175
+ if (message.adminDismissTimer || message.classList.contains('is-removing')) {
176
+ return;
177
+ }
178
+
179
+ if (message.adminTimerRemaining <= 0) {
180
+ dismissAdminMessage(message);
181
+ return;
182
+ }
183
+
184
+ window.requestAnimationFrame(() => {
185
+ setAdminMessageTimer(message, message.adminTimerRemaining);
186
+ });
187
+ }
188
+
149
189
  function startAdminMessageTimers() {
150
190
  adminMessages.forEach((message) => {
151
191
  message.classList.remove('is-counting', 'is-removing');
152
192
  message.style.transition = 'none';
153
193
  message.style.backgroundSize = '0 100%';
154
194
  message.offsetWidth;
195
+ message.addEventListener('mouseenter', () => pauseAdminMessageTimer(message));
196
+ message.addEventListener('mouseleave', () => resumeAdminMessageTimer(message));
155
197
 
156
198
  window.requestAnimationFrame(() => {
157
- message.style.transition = '';
158
- message.style.backgroundSize = '';
199
+ message.style.transition = `background-size ${adminMessageDuration}ms linear, opacity 160ms ease, transform 160ms ease`;
159
200
  window.requestAnimationFrame(() => {
160
201
  message.classList.add('is-counting');
202
+ setAdminMessageTimer(message, adminMessageDuration);
161
203
  });
162
204
  });
163
- window.setTimeout(() => dismissAdminMessage(message), 5000);
164
205
  });
165
206
  }
166
207
 
208
+ function syncChangelistActions() {
209
+ const form = document.getElementById('changelist-form');
210
+ const actions = Array.from(document.querySelectorAll('#changelist .actions'));
211
+
212
+ if (!form || actions.length === 0) {
213
+ return;
214
+ }
215
+
216
+ const checkboxes = Array.from(form.querySelectorAll('input.action-select[type="checkbox"]'));
217
+ const primaryActionBar = actions[0];
218
+
219
+ function syncStickyActionBar() {
220
+ if (!primaryActionBar || !form.classList.contains('has-selected-actions')) {
221
+ form.classList.remove('has-sticky-actions');
222
+ return;
223
+ }
224
+
225
+ const formRect = form.getBoundingClientRect();
226
+ const contentBox = form.closest('.changelist-form-container > div') || form.parentElement || form;
227
+ const contentRect = contentBox.getBoundingClientRect();
228
+ const shouldStick = formRect.top < 12 && formRect.bottom > 84;
229
+ form.classList.toggle('has-sticky-actions', shouldStick);
230
+
231
+ if (!shouldStick) {
232
+ return;
233
+ }
234
+
235
+ const left = Math.max(contentRect.left + 12, 12);
236
+ const rightEdge = Math.min(contentRect.right - 12, window.innerWidth - 12);
237
+ const width = Math.max(rightEdge - left, 0);
238
+ primaryActionBar.style.setProperty('--admin-actions-fixed-left', `${left}px`);
239
+ primaryActionBar.style.setProperty('--admin-actions-fixed-width', `${width}px`);
240
+ }
241
+
242
+ actions.forEach((actionBar, index) => {
243
+ actionBar.classList.toggle('admin-actions--duplicate', index > 0);
244
+
245
+ if (index === 0 && !actionBar.querySelector('.admin-actions__clear')) {
246
+ const clearButton = document.createElement('button');
247
+ clearButton.type = 'button';
248
+ clearButton.className = 'admin-actions__clear';
249
+ clearButton.setAttribute('aria-label', 'Clear selected rows');
250
+ clearButton.addEventListener('click', () => {
251
+ checkboxes.forEach((checkbox) => {
252
+ checkbox.checked = false;
253
+ checkbox.closest('tr')?.classList.remove('selected');
254
+ checkbox.dispatchEvent(new Event('change', { bubbles: true }));
255
+ });
256
+ const actionToggle = form.querySelector('#action-toggle');
257
+ if (actionToggle) {
258
+ actionToggle.checked = false;
259
+ }
260
+ updateActionsVisibility();
261
+ });
262
+ actionBar.prepend(clearButton);
263
+ }
264
+ });
265
+
266
+ function updateActionsVisibility() {
267
+ const hasSelectedRows = checkboxes.some((checkbox) => checkbox.checked);
268
+ form.classList.toggle('has-selected-actions', hasSelectedRows);
269
+ actions.forEach((actionBar) => {
270
+ actionBar.setAttribute('aria-hidden', String(!hasSelectedRows));
271
+ });
272
+ syncStickyActionBar();
273
+ }
274
+
275
+ form.addEventListener('change', (event) => {
276
+ if (event.target.matches('input[type="checkbox"]')) {
277
+ updateActionsVisibility();
278
+ }
279
+ });
280
+ window.addEventListener('scroll', syncStickyActionBar, { passive: true });
281
+ window.addEventListener('resize', syncStickyActionBar);
282
+ updateActionsVisibility();
283
+ }
284
+
167
285
  function applyTheme(theme) {
168
286
  if (!validThemeValues.has(theme)) {
169
287
  return;
@@ -346,6 +464,7 @@
346
464
  setCollapsed(storedCollapsed === 'true', false);
347
465
  setMobileOpen(false);
348
466
  startAdminMessageTimers();
467
+ syncChangelistActions();
349
468
  applyStoredOpenSections();
350
469
 
351
470
  const storedTheme = localStorage.getItem('theme');
@@ -18,6 +18,17 @@
18
18
  <symbol id="admin-icon-clock" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9" /><path d="M12 7v6l4 2" /></symbol>
19
19
  <symbol id="admin-icon-sun" viewBox="0 0 24 24"><circle cx="12" cy="12" r="4" /><path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M4.93 19.07l1.41-1.41M17.66 6.34l1.41-1.41" /></symbol>
20
20
  <symbol id="admin-icon-moon" viewBox="0 0 24 24"><path d="M21 12.8A8.5 8.5 0 1 1 11.2 3 6.7 6.7 0 0 0 21 12.8Z" /></symbol>
21
+ <symbol id="admin-icon-options" viewBox="0 0 512 512">
22
+ <line x1="368" y1="128" x2="448" y2="128" />
23
+ <line x1="64" y1="128" x2="304" y2="128" />
24
+ <line x1="368" y1="384" x2="448" y2="384" />
25
+ <line x1="64" y1="384" x2="304" y2="384" />
26
+ <line x1="208" y1="256" x2="448" y2="256" />
27
+ <line x1="64" y1="256" x2="144" y2="256" />
28
+ <circle cx="336" cy="128" r="32" />
29
+ <circle cx="176" cy="256" r="32" />
30
+ <circle cx="336" cy="384" r="32" />
31
+ </symbol>
21
32
  <symbol id="admin-icon-external" viewBox="0 0 24 24"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" /><path d="M15 3h6v6M10 14 21 3" /></symbol>
22
33
  <symbol id="admin-icon-lock" viewBox="0 0 24 24"><rect x="4" y="11" width="16" height="10" rx="2" /><path d="M8 11V7a4 4 0 0 1 8 0v4" /></symbol>
23
34
  <symbol id="admin-icon-log-out" viewBox="0 0 24 24"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" /><path d="M16 17l5-5-5-5M21 12H9" /></symbol>
@@ -81,7 +92,7 @@
81
92
  {% first_group_name user as group_name %}
82
93
  <details class="admin-sidebar__account">
83
94
  <summary class="admin-sidebar__account-summary">
84
- <span class="admin-sidebar__avatar" aria-hidden="true">{{ user.get_username|first|upper }}</span>
95
+ <svg class="admin-sidebar__account-icon" aria-hidden="true"><use href="#admin-icon-options"></use></svg>
85
96
  <span class="admin-sidebar__account-copy">
86
97
  <span class="admin-sidebar__username">{{ user.get_username }}</span>
87
98
  {% if group_name %}
@@ -116,6 +127,13 @@
116
127
  <span>Change password</span>
117
128
  </a>
118
129
  {% endif %}
130
+ <a class="admin-sidebar__donation-link donation-button" href="https://www.vanta-admin.org/en/donate/" target="_blank" rel="noopener noreferrer" aria-label="Buy me a coffee">
131
+ <span class="donation-button__cup" aria-hidden="true">
132
+ <span class="donation-button__steam"></span>
133
+ </span>
134
+ <span class="donation-button__label">Buy me a coffee</span>
135
+ <svg class="admin-sidebar__external-icon" aria-hidden="true"><use href="#admin-icon-external"></use></svg>
136
+ </a>
119
137
  <span class="admin-sidebar__account-divider" aria-hidden="true"></span>
120
138
  <form method="post" action="{% url 'admin:logout' %}">
121
139
  {% csrf_token %}
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vanta-admin
3
- Version: 0.1.7
3
+ Version: 0.2.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
@@ -32,20 +32,26 @@ standard Django admin behavior.
32
32
 
33
33
  ## What It Adds
34
34
 
35
- - A fixed sidebar with persistent collapsed state and remembered open sections.
36
- - Light and dark theme support through CSS tokens.
37
- - A flatter visual pass that removes heavy shadows and blur effects from the
38
- admin shell, login screens, cards, filters, and two-factor auth views.
39
- - A denser dashboard, changelist, filter, message, and form presentation.
40
- - Auto-dismissing admin messages with success, warning, and error treatment.
41
- - Custom single-select controls for changelist actions and normal admin form
42
- selects, while native multi-selects stay unchanged.
43
- - Default icons for common admin apps, models, and recent actions, including
44
- incident and uptime-monitor style models.
45
- - A packaged admin favicon loaded through the theme.
46
- - A responsive mobile sidebar overlay.
47
- - Optional themed templates for `django-two-factor-auth` login, setup completion,
48
- account security, and backup-token flows.
35
+ - A cleaner admin layout with a fixed sidebar that remembers how you left it.
36
+ - Light and dark mode, using the same calm Vanta look across the admin.
37
+ - Less visual noise on the main admin screens, login pages, cards, filters, and
38
+ two-factor auth pages.
39
+ - Denser dashboards, tables, filters, messages, and forms so admin work feels
40
+ easier to scan.
41
+ - Admin messages that appear clearly, pause while hovered, and dismiss
42
+ themselves after a short time.
43
+ - Changelist actions that appear only when rows are selected, stay available
44
+ while scrolling, and include a clear-selected control.
45
+ - Styled select controls for common admin dropdowns, while Django's native
46
+ multi-select fields stay unchanged.
47
+ - Built-in icons for common admin areas, models, and recent actions.
48
+ - A cleaner account menu with theme controls, project links, password access,
49
+ logout, and a Vanta support link.
50
+ - A packaged favicon for the admin.
51
+ - A mobile sidebar overlay that makes the admin easier to use on smaller
52
+ screens.
53
+ - Optional matching templates for `django-two-factor-auth` login, setup
54
+ completion, account security, and backup-token screens.
49
55
 
50
56
  Vanta Admin does not replace Django admin, change your permissions, or configure
51
57
  two-factor authentication for you. If your project uses `django-two-factor-auth`,
File without changes
File without changes
File without changes