django-admin-modern-view 0.2.1__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 (35) hide show
  1. django_admin_modern_view/__init__.py +1 -0
  2. django_admin_modern_view/apps.py +10 -0
  3. django_admin_modern_view/checks.py +55 -0
  4. django_admin_modern_view/services/__init__.py +0 -0
  5. django_admin_modern_view/services/command_registry.py +78 -0
  6. django_admin_modern_view/services/search.py +101 -0
  7. django_admin_modern_view/static/django_admin_modern_view/css/base.css +69 -0
  8. django_admin_modern_view/static/django_admin_modern_view/css/command-palette.css +151 -0
  9. django_admin_modern_view/static/django_admin_modern_view/css/forms.css +189 -0
  10. django_admin_modern_view/static/django_admin_modern_view/css/layout.css +315 -0
  11. django_admin_modern_view/static/django_admin_modern_view/css/sidebar.css +412 -0
  12. django_admin_modern_view/static/django_admin_modern_view/css/tables.css +402 -0
  13. django_admin_modern_view/static/django_admin_modern_view/css/utilities.css +236 -0
  14. django_admin_modern_view/static/django_admin_modern_view/css/variables.css +208 -0
  15. django_admin_modern_view/static/django_admin_modern_view/js/app.js +72 -0
  16. django_admin_modern_view/static/django_admin_modern_view/js/command-palette.js +226 -0
  17. django_admin_modern_view/static/django_admin_modern_view/js/dashboard.js +87 -0
  18. django_admin_modern_view/static/django_admin_modern_view/js/forms.js +0 -0
  19. django_admin_modern_view/static/django_admin_modern_view/js/global-search.js +29 -0
  20. django_admin_modern_view/static/django_admin_modern_view/js/notifications.js +58 -0
  21. django_admin_modern_view/static/django_admin_modern_view/js/shortcuts.js +76 -0
  22. django_admin_modern_view/static/django_admin_modern_view/js/sidebar.js +158 -0
  23. django_admin_modern_view/static/django_admin_modern_view/js/table.js +234 -0
  24. django_admin_modern_view/static/django_admin_modern_view/js/utils.js +105 -0
  25. django_admin_modern_view/templates/admin/base_site.html +114 -0
  26. django_admin_modern_view/templates/admin/index.html +58 -0
  27. django_admin_modern_view/templatetags/__init__.py +0 -0
  28. django_admin_modern_view/templatetags/django_admin_modern_view_tags.py +36 -0
  29. django_admin_modern_view/urls.py +10 -0
  30. django_admin_modern_view/views.py +18 -0
  31. django_admin_modern_view-0.2.1.dist-info/METADATA +192 -0
  32. django_admin_modern_view-0.2.1.dist-info/RECORD +35 -0
  33. django_admin_modern_view-0.2.1.dist-info/WHEEL +5 -0
  34. django_admin_modern_view-0.2.1.dist-info/licenses/LICENSE +21 -0
  35. django_admin_modern_view-0.2.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1 @@
1
+ __version__ = "0.2.1"
@@ -0,0 +1,10 @@
1
+ from django.apps import AppConfig
2
+
3
+
4
+ class DjangoAdminModernViewConfig(AppConfig):
5
+ default_auto_field = "django.db.models.BigAutoField"
6
+ name = "django_admin_modern_view"
7
+ verbose_name = "Django Admin Modern View"
8
+
9
+ def ready(self):
10
+ from . import checks # noqa: F401 (registers Django system checks)
@@ -0,0 +1,55 @@
1
+ """Django system checks that catch the one setup mistake this package
2
+ can't work around: app ordering.
3
+
4
+ Django's app_dirs template loader resolves ``admin/*.html`` overrides by
5
+ walking INSTALLED_APPS in order and using the first match. If
6
+ django_admin_modern_view isn't listed before django.contrib.admin, its template
7
+ overrides silently lose to Django's own — the admin just looks
8
+ unstyled, with no error to point at why.
9
+ """
10
+ from django.apps import apps
11
+ from django.conf import settings
12
+ from django.core.checks import Warning, register
13
+
14
+
15
+ @register()
16
+ def check_app_order(app_configs, **kwargs):
17
+ installed = list(settings.INSTALLED_APPS)
18
+ try:
19
+ admin_index = next(
20
+ i for i, name in enumerate(installed)
21
+ if name == "django.contrib.admin" or name.startswith("django.contrib.admin.")
22
+ )
23
+ except StopIteration:
24
+ return [] # django.contrib.admin isn't installed; nothing to check
25
+
26
+ try:
27
+ our_index = next(
28
+ i for i, name in enumerate(installed)
29
+ if name == "django_admin_modern_view" or name.startswith("django_admin_modern_view.")
30
+ )
31
+ except StopIteration:
32
+ our_index = None # resolved via AppConfig path instead of the bare name
33
+
34
+ if our_index is None:
35
+ try:
36
+ our_index = installed.index(apps.get_app_config("django_admin_modern_view").name)
37
+ except LookupError:
38
+ return []
39
+
40
+ if our_index > admin_index:
41
+ return [
42
+ Warning(
43
+ "django_admin_modern_view is listed after django.contrib.admin in "
44
+ "INSTALLED_APPS.",
45
+ hint=(
46
+ "Move 'django_admin_modern_view' above 'django.contrib.admin' in "
47
+ "INSTALLED_APPS. Django's template loader picks the "
48
+ "first admin/*.html it finds by app order, so this "
49
+ "package's overrides (topbar, command palette, dashboard) "
50
+ "will otherwise be silently ignored."
51
+ ),
52
+ id="django_admin_modern_view.W001",
53
+ )
54
+ ]
55
+ return []
File without changes
@@ -0,0 +1,78 @@
1
+ """Central registry for the Modern Admin command palette (Ctrl+K).
2
+
3
+ Commands are plain dicts so they serialize straight to JSON for the
4
+ frontend. Registered commands are merged with commands auto-derived
5
+ from the Django admin site (navigation + "create" actions) on every
6
+ request, so palette content always reflects the logged-in user's
7
+ permissions.
8
+ """
9
+ from django.urls import reverse
10
+
11
+ _registered_commands = []
12
+
13
+
14
+ def register_command(id, label, category="General", shortcut=None, url=None, icon=None, keywords=""):
15
+ """Register a custom command shown in the command palette.
16
+
17
+ Example:
18
+ register_command(
19
+ id="create_user",
20
+ label="Create User",
21
+ category="Actions",
22
+ url="/admin/auth/user/add/",
23
+ )
24
+ """
25
+ _registered_commands.append({
26
+ "id": id,
27
+ "label": label,
28
+ "category": category,
29
+ "shortcut": shortcut,
30
+ "url": url,
31
+ "icon": icon or "",
32
+ "keywords": keywords,
33
+ })
34
+
35
+
36
+ def get_registered_commands():
37
+ return list(_registered_commands)
38
+
39
+
40
+ def get_commands_for_request(request):
41
+ """Build the full palette command list for the current user."""
42
+ from django.contrib import admin
43
+
44
+ commands = [{
45
+ "id": "nav:dashboard",
46
+ "label": "Dashboard",
47
+ "category": "Navigation",
48
+ "shortcut": None,
49
+ "url": reverse("admin:index"),
50
+ "icon": "home",
51
+ "keywords": "home dashboard",
52
+ }]
53
+
54
+ for app in admin.site.get_app_list(request):
55
+ for model in app["models"]:
56
+ if model.get("admin_url"):
57
+ commands.append({
58
+ "id": f"nav:{app['app_label']}.{model['object_name']}",
59
+ "label": model["name"],
60
+ "category": "Navigation",
61
+ "shortcut": None,
62
+ "url": model["admin_url"],
63
+ "icon": "list",
64
+ "keywords": f"{app['name']} {model['name']}",
65
+ })
66
+ if model.get("add_url"):
67
+ commands.append({
68
+ "id": f"create:{app['app_label']}.{model['object_name']}",
69
+ "label": f"Create {model['name']}",
70
+ "category": "Actions",
71
+ "shortcut": None,
72
+ "url": model["add_url"],
73
+ "icon": "plus",
74
+ "keywords": f"add new {model['name']}",
75
+ })
76
+
77
+ commands.extend(get_registered_commands())
78
+ return commands
@@ -0,0 +1,101 @@
1
+ """Global search service (Phase 5).
2
+
3
+ Searches across models registered with the Django admin. Any
4
+ ModelAdmin that defines `search_fields` is auto-registered on app
5
+ startup so search works with zero extra config; `register_global_search`
6
+ lets you register a model explicitly (custom fields/label) or a model
7
+ that isn't registered with the admin at all.
8
+
9
+ Kept deliberately decoupled from the ORM implementation so it can
10
+ later be swapped for Postgres full text search / Meilisearch /
11
+ OpenSearch without touching the view or the frontend contract.
12
+ """
13
+ from django.db.models import Q
14
+
15
+ _registry = []
16
+ _autodiscovered = False
17
+
18
+
19
+ def register_global_search(model, fields, label=None):
20
+ if any(entry["model"] is model for entry in _registry):
21
+ return
22
+ _registry.append({
23
+ "model": model,
24
+ "fields": list(fields),
25
+ "label": label or str(model._meta.verbose_name_plural).title(),
26
+ })
27
+
28
+
29
+ def autodiscover_from_admin():
30
+ """Auto-register every ModelAdmin that defines search_fields.
31
+
32
+ Called lazily (on first search) rather than from AppConfig.ready():
33
+ django_admin_modern_view is listed before django.contrib.admin in
34
+ INSTALLED_APPS so that its template overrides win, but that also
35
+ means its ready() runs before admin.autodiscover() has populated
36
+ admin.site._registry.
37
+ """
38
+ global _autodiscovered
39
+ from django.contrib import admin
40
+
41
+ for model, model_admin in admin.site._registry.items():
42
+ search_fields = getattr(model_admin, "search_fields", None)
43
+ if search_fields:
44
+ register_global_search(model, search_fields)
45
+ _autodiscovered = True
46
+
47
+
48
+ def _ensure_autodiscovered():
49
+ if not _autodiscovered:
50
+ autodiscover_from_admin()
51
+
52
+
53
+ def _q_for_field(field, query):
54
+ if field.startswith("^"):
55
+ return Q(**{f"{field[1:]}__istartswith": query})
56
+ if field.startswith("="):
57
+ return Q(**{f"{field[1:]}__iexact": query})
58
+ if field.startswith("@"):
59
+ return Q(**{f"{field[1:]}__search": query})
60
+ return Q(**{f"{field}__icontains": query})
61
+
62
+
63
+ def _admin_url_for(model, obj):
64
+ from django.urls import NoReverseMatch, reverse
65
+
66
+ opts = model._meta
67
+ try:
68
+ return reverse(f"admin:{opts.app_label}_{opts.model_name}_change", args=[obj.pk])
69
+ except NoReverseMatch:
70
+ return None
71
+
72
+
73
+ def global_search(request, query, per_model_limit=5):
74
+ from django.contrib import admin
75
+
76
+ _ensure_autodiscovered()
77
+ results = []
78
+ for entry in _registry:
79
+ model = entry["model"]
80
+ model_admin = admin.site._registry.get(model)
81
+ if model_admin is not None and not model_admin.has_view_permission(request):
82
+ continue
83
+
84
+ q_obj = Q()
85
+ for field in entry["fields"]:
86
+ q_obj |= _q_for_field(field, query)
87
+
88
+ try:
89
+ queryset = model._default_manager.filter(q_obj)[:per_model_limit]
90
+ except Exception:
91
+ continue
92
+
93
+ for obj in queryset:
94
+ results.append({
95
+ "type": entry["label"],
96
+ "label": str(obj),
97
+ "description": entry["label"],
98
+ "url": _admin_url_for(model, obj),
99
+ })
100
+
101
+ return results
@@ -0,0 +1,69 @@
1
+ /* Global typography + surface tweaks layered on top of Django's admin/css/base.css */
2
+
3
+ body {
4
+ font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
5
+ -webkit-font-smoothing: antialiased;
6
+ }
7
+
8
+ a:link,
9
+ a:visited {
10
+ text-decoration: none;
11
+ }
12
+
13
+ a:hover {
14
+ text-decoration: underline;
15
+ }
16
+
17
+ #content {
18
+ padding: 24px 32px;
19
+ }
20
+
21
+ #content h1 {
22
+ font-size: 21px;
23
+ font-weight: 700;
24
+ letter-spacing: -0.01em;
25
+ color: var(--body-loud-color);
26
+ }
27
+
28
+ .breadcrumbs {
29
+ font-size: 12.5px;
30
+ padding: 10px 0 0;
31
+ }
32
+
33
+ .module {
34
+ border-radius: var(--radius-md);
35
+ box-shadow: var(--shadow-sm);
36
+ border: 1px solid var(--border-color);
37
+ overflow: hidden;
38
+ }
39
+
40
+ .module h2,
41
+ .module caption,
42
+ .inline-group h2 {
43
+ border-radius: 0;
44
+ }
45
+
46
+ .button,
47
+ input[type="submit"],
48
+ input[type="button"],
49
+ .submit-row input,
50
+ a.button {
51
+ border-radius: var(--radius-sm);
52
+ transition: background-color 0.15s ease, box-shadow 0.15s ease;
53
+ }
54
+
55
+ .object-tools a,
56
+ .object-tools a:link {
57
+ border-radius: var(--radius-sm);
58
+ }
59
+
60
+ /* Focus visibility (Section 20: accessibility) */
61
+ a:focus-visible,
62
+ button:focus-visible,
63
+ input:focus-visible,
64
+ select:focus-visible,
65
+ textarea:focus-visible,
66
+ [tabindex]:focus-visible {
67
+ outline: 2px solid var(--primary);
68
+ outline-offset: 2px;
69
+ }
@@ -0,0 +1,151 @@
1
+ /* Phase 4: command palette + generic modal/overlay styles */
2
+
3
+ .ma-overlay {
4
+ position: fixed;
5
+ inset: 0;
6
+ z-index: 1000;
7
+ display: none;
8
+ align-items: flex-start;
9
+ justify-content: center;
10
+ padding-top: 12vh;
11
+ background: var(--overlay-bg);
12
+ }
13
+
14
+ .ma-overlay.ma-open {
15
+ display: flex;
16
+ }
17
+
18
+ .ma-palette {
19
+ width: 100%;
20
+ max-width: 600px;
21
+ max-height: 60vh;
22
+ display: flex;
23
+ flex-direction: column;
24
+ background: var(--surface-raised);
25
+ border-radius: var(--radius-lg);
26
+ box-shadow: var(--shadow-lg);
27
+ overflow: hidden;
28
+ }
29
+
30
+ .ma-palette__input-row {
31
+ display: flex;
32
+ align-items: center;
33
+ gap: 10px;
34
+ padding: 14px 16px;
35
+ border-bottom: 1px solid var(--border-color);
36
+ }
37
+
38
+ .ma-palette__input-row svg {
39
+ flex: 0 0 auto;
40
+ color: var(--body-quiet-color);
41
+ }
42
+
43
+ .ma-palette__input {
44
+ flex: 1 1 auto;
45
+ border: none;
46
+ background: transparent;
47
+ color: var(--body-fg);
48
+ font-size: 15px;
49
+ outline: none;
50
+ }
51
+
52
+ .ma-palette__results {
53
+ overflow-y: auto;
54
+ padding: 8px;
55
+ }
56
+
57
+ .ma-palette__group-label {
58
+ padding: 8px 10px 4px;
59
+ font-size: 11px;
60
+ font-weight: 700;
61
+ letter-spacing: 0.05em;
62
+ text-transform: uppercase;
63
+ color: var(--body-quiet-color);
64
+ }
65
+
66
+ .ma-palette__item {
67
+ display: flex;
68
+ align-items: center;
69
+ gap: 10px;
70
+ padding: 9px 10px;
71
+ border-radius: var(--radius-sm);
72
+ cursor: pointer;
73
+ color: var(--body-fg);
74
+ font-size: 13.5px;
75
+ }
76
+
77
+ .ma-palette__item:hover,
78
+ .ma-palette__item.ma-active {
79
+ background: var(--selected-bg);
80
+ }
81
+
82
+ .ma-palette__item-icon {
83
+ flex: 0 0 auto;
84
+ width: 16px;
85
+ text-align: center;
86
+ color: var(--body-quiet-color);
87
+ }
88
+
89
+ .ma-palette__item-label {
90
+ flex: 1 1 auto;
91
+ }
92
+
93
+ .ma-palette__item-desc {
94
+ color: var(--body-quiet-color);
95
+ font-size: 12px;
96
+ }
97
+
98
+ .ma-palette__empty {
99
+ padding: 24px;
100
+ text-align: center;
101
+ color: var(--body-quiet-color);
102
+ font-size: 13px;
103
+ }
104
+
105
+ .ma-palette__footer {
106
+ display: flex;
107
+ gap: 14px;
108
+ padding: 8px 14px;
109
+ border-top: 1px solid var(--border-color);
110
+ font-size: 11px;
111
+ color: var(--body-quiet-color);
112
+ }
113
+
114
+ /* Generic modal (shortcut help, confirmations) */
115
+ .ma-modal {
116
+ width: 100%;
117
+ max-width: 480px;
118
+ background: var(--surface-raised);
119
+ border-radius: var(--radius-lg);
120
+ box-shadow: var(--shadow-lg);
121
+ padding: 20px 22px;
122
+ }
123
+
124
+ .ma-modal__title {
125
+ margin: 0 0 14px;
126
+ font-size: 15px;
127
+ font-weight: 600;
128
+ }
129
+
130
+ .ma-shortcut-row {
131
+ display: flex;
132
+ justify-content: space-between;
133
+ padding: 6px 0;
134
+ font-size: 13px;
135
+ }
136
+
137
+ .ma-shortcut-row + .ma-shortcut-row {
138
+ border-top: 1px solid var(--hairline-color);
139
+ }
140
+
141
+ .ma-shortcut-group + .ma-shortcut-group {
142
+ margin-top: 14px;
143
+ }
144
+
145
+ .ma-shortcut-group__title {
146
+ font-size: 11px;
147
+ font-weight: 700;
148
+ letter-spacing: 0.05em;
149
+ text-transform: uppercase;
150
+ color: var(--body-quiet-color);
151
+ }
@@ -0,0 +1,189 @@
1
+ /* Phase 8: enhanced forms — sticky save bar + dirty-state pill.
2
+ Works with Django's existing .submit-row, no template override. */
3
+
4
+ .submit-row {
5
+ position: sticky;
6
+ /* Stop above the fixed .ma-statusbar (layout.css) instead of
7
+ underneath/behind it — both pinning to bottom:0 made them
8
+ overlap. */
9
+ bottom: var(--statusbar-height);
10
+ z-index: 100;
11
+ border-radius: var(--radius-md);
12
+ box-shadow: var(--shadow-md);
13
+ align-items: center;
14
+ }
15
+
16
+ .ma-dirty-indicator {
17
+ display: none;
18
+ align-items: center;
19
+ gap: 6px;
20
+ margin-right: auto;
21
+ padding-left: 4px;
22
+ font-size: 12.5px;
23
+ color: var(--warning);
24
+ }
25
+
26
+ .ma-dirty-indicator::before {
27
+ content: "";
28
+ width: 7px;
29
+ height: 7px;
30
+ border-radius: 50%;
31
+ background: currentColor;
32
+ }
33
+
34
+ .submit-row.ma-dirty .ma-dirty-indicator {
35
+ display: flex;
36
+ }
37
+
38
+ fieldset.module {
39
+ margin-bottom: 16px;
40
+ }
41
+
42
+ .form-row {
43
+ border-bottom-color: var(--hairline-color);
44
+ }
45
+
46
+ /* Modern text inputs / textareas / selects. Excludes buttons and the
47
+ checkbox/radio/file inputs, which keep their native controls. */
48
+ input:not([type="submit"]):not([type="button"]):not([type="checkbox"]):not([type="radio"]):not([type="file"]),
49
+ textarea,
50
+ select,
51
+ .vTextField,
52
+ .vDateField,
53
+ .vTimeField,
54
+ .vURLField {
55
+ background: var(--surface-raised);
56
+ border: 1px solid var(--border-color);
57
+ border-radius: var(--radius-sm);
58
+ padding: 8px 10px;
59
+ font-size: 13.5px;
60
+ }
61
+
62
+ select {
63
+ padding-top: 6px;
64
+ padding-bottom: 6px;
65
+ }
66
+
67
+ select[multiple] {
68
+ padding: 6px;
69
+ }
70
+
71
+ input:focus,
72
+ textarea:focus,
73
+ select:focus {
74
+ border-color: var(--primary);
75
+ box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.15);
76
+ outline: none;
77
+ }
78
+
79
+ html[data-theme="dark"] input:focus,
80
+ html[data-theme="dark"] textarea:focus,
81
+ html[data-theme="dark"] select:focus {
82
+ box-shadow: 0 0 0 3px rgba(129, 140, 248, 0.25);
83
+ }
84
+
85
+ .errorlist {
86
+ border-radius: var(--radius-sm);
87
+ overflow: hidden;
88
+ }
89
+
90
+ /* Related-object widgets: FK <select> + add/change/view icon buttons */
91
+ .related-widget-wrapper {
92
+ align-items: center;
93
+ }
94
+
95
+ .related-widget-wrapper select {
96
+ min-width: 260px;
97
+ }
98
+
99
+ .related-widget-wrapper-link {
100
+ display: inline-flex;
101
+ align-items: center;
102
+ justify-content: center;
103
+ width: 28px;
104
+ height: 28px;
105
+ border-radius: var(--radius-sm);
106
+ background: var(--darkened-bg);
107
+ border: 1px solid var(--border-color);
108
+ }
109
+
110
+ .related-widget-wrapper-link:hover {
111
+ background: var(--selected-bg);
112
+ }
113
+
114
+ .related-widget-wrapper-link img {
115
+ width: 14px;
116
+ height: 14px;
117
+ }
118
+
119
+ /* Filter-horizontal / filter-vertical (m2m selector) */
120
+ .selector-available-title,
121
+ .selector-chosen-title {
122
+ border-radius: var(--radius-sm) var(--radius-sm) 0 0;
123
+ border: 1px solid var(--border-color);
124
+ border-bottom: none;
125
+ }
126
+
127
+ .selector-chosen-title {
128
+ background: var(--primary);
129
+ color: var(--primary-fg);
130
+ }
131
+
132
+ .selector-chosen-title label {
133
+ color: var(--primary-fg);
134
+ }
135
+
136
+ .selector-available-title {
137
+ background: var(--darkened-bg);
138
+ }
139
+
140
+ .selector .selector-filter {
141
+ background: var(--surface-raised);
142
+ border-color: var(--border-color);
143
+ }
144
+
145
+ .selector select {
146
+ background: var(--surface-raised);
147
+ border: 1px solid var(--border-color);
148
+ border-radius: 0 0 var(--radius-sm) var(--radius-sm);
149
+ }
150
+
151
+ .selector ul.selector-chooser {
152
+ background: var(--surface-raised);
153
+ border: 1px solid var(--border-color);
154
+ border-radius: var(--radius-lg);
155
+ box-shadow: var(--shadow-sm);
156
+ }
157
+
158
+ .selector-add,
159
+ .selector-remove {
160
+ border-radius: var(--radius-sm);
161
+ }
162
+
163
+ .selector-chosen .list-footer-display {
164
+ border-radius: 0 0 var(--radius-sm) var(--radius-sm);
165
+ }
166
+
167
+ /* Icon-only chrome (related-object icons, calendar/clock shortcuts, search
168
+ lookup icon) ships as dark-stroke SVGs meant for a light page — Django's
169
+ own dark_mode.css doesn't invert them, so they go nearly invisible on a
170
+ dark surface. Brighten them only in dark mode. */
171
+ html[data-theme="dark"] .related-widget-wrapper-link img,
172
+ html[data-theme="dark"] .related-lookup,
173
+ html[data-theme="dark"] .selector-add,
174
+ html[data-theme="dark"] .selector-remove,
175
+ html[data-theme="dark"] .datetimeshortcuts img,
176
+ html[data-theme="dark"] .calendar-shortcuts img {
177
+ filter: invert(1) brightness(1.5);
178
+ }
179
+
180
+ @media (prefers-color-scheme: dark) {
181
+ :root:not([data-theme="light"]) .related-widget-wrapper-link img,
182
+ :root:not([data-theme="light"]) .related-lookup,
183
+ :root:not([data-theme="light"]) .selector-add,
184
+ :root:not([data-theme="light"]) .selector-remove,
185
+ :root:not([data-theme="light"]) .datetimeshortcuts img,
186
+ :root:not([data-theme="light"]) .calendar-shortcuts img {
187
+ filter: invert(1) brightness(1.5);
188
+ }
189
+ }