micro-sidebar 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,40 @@
1
+ Metadata-Version: 2.4
2
+ Name: micro_sidebar
3
+ Version: 1.0.0
4
+ Summary: A reusable Django sidebar for Web Apps
5
+ Home-page: https://github.com/debeski/micro-sidebar
6
+ Author: DeBeski
7
+ Author-email: DeBeski <debeski1@gmail.com>
8
+ License: MIT
9
+ Project-URL: Homepage, https://github.com/debeski/micro-sidebar
10
+ Classifier: Framework :: Django
11
+ Classifier: Framework :: Django :: 5
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.8
14
+ Classifier: Programming Language :: Python :: 3.9
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Programming Language :: Python :: 3.14
20
+ Classifier: License :: OSI Approved :: MIT License
21
+ Classifier: Operating System :: OS Independent
22
+ Requires-Python: >=3.9
23
+ Description-Content-Type: text/markdown
24
+ Requires-Dist: Django>=5.1
25
+ Dynamic: author
26
+ Dynamic: home-page
27
+ Dynamic: requires-python
28
+
29
+ # Micro Sidebar
30
+
31
+ A reusable Django sidebar app.
32
+
33
+ ## Installation
34
+
35
+ 1. Add `sidebar` to your `INSTALLED_APPS` setting.
36
+ 2. Include the URLconf in your project urls.py:
37
+ ```python
38
+ path('sidebar/', include('sidebar.urls')),
39
+ ```
40
+ 3. Override the content by creating `templates/sidebar/content.html` in your project. See `sidebar/templates/sidebar/example_content.html` for a starting point.
@@ -0,0 +1,12 @@
1
+ sidebar/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ sidebar/apps.py,sha256=4UjKXHoBGKRLxpC9AY6eaq8YZx8unirUz_8u-IrlfVQ,145
3
+ sidebar/urls.py,sha256=UL_9e1RLNMxZXkah65m7GRU1dbViZRGeNPBIiSZpOYg,142
4
+ sidebar/views.py,sha256=MebyJ1ZiylSOPESXFkkQ8QTg-ClrkJn-oYLN6KrcgiM,418
5
+ sidebar/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
+ sidebar/static/sidebar/style.css,sha256=aEbc4yPFaQBpMoYnJi575u077pHzej7FLFP8kgRnfBk,2105
7
+ sidebar/static/sidebar/js/sidebar.js,sha256=EHQOsJWujpoV7A3K5WAwGx1nIV_oNh5yQ4tM3-PrbsI,5930
8
+ sidebar/templates/sidebar/content.html,sha256=hSqgzvuWZRxlUN1-zC-Oovppd_rbnCylxQmfRdGIbhY,1298
9
+ micro_sidebar-1.0.0.dist-info/METADATA,sha256=zdEyRYYTjtmcMfj4TGMkqPQe6j-vUE2dE0shwBpTq0w,1412
10
+ micro_sidebar-1.0.0.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
11
+ micro_sidebar-1.0.0.dist-info/top_level.txt,sha256=ih69sjMhU1wOB9HzUV90yEY98aiPuGhzPBBBE-YtJ3w,8
12
+ micro_sidebar-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.10.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ sidebar
sidebar/__init__.py ADDED
File without changes
sidebar/apps.py ADDED
@@ -0,0 +1,5 @@
1
+ from django.apps import AppConfig
2
+
3
+ class SidebarConfig(AppConfig):
4
+ default_auto_field = 'django.db.models.BigAutoField'
5
+ name = 'sidebar'
File without changes
@@ -0,0 +1,156 @@
1
+ document.addEventListener("DOMContentLoaded", function () {
2
+ const sidebar = document.getElementById("sidebar");
3
+ const sidebarToggle = document.getElementById("sidebarToggle");
4
+
5
+ // Read config from data attribute on sidebar or a global config
6
+ // Assuming sidebar has data attributes, or we can look for a meta tag
7
+ const sidebarConfigStr = sidebar.getAttribute('data-sidebar-config');
8
+ let sidebarConfig = {};
9
+ if (sidebarConfigStr) {
10
+ try {
11
+ sidebarConfig = JSON.parse(sidebarConfigStr.replace(/'/g, '"')); // Simple parse attempt, better to use valid JSON in attribute
12
+ } catch (e) {
13
+ console.error("Error parsing sidebar config", e);
14
+ }
15
+ }
16
+
17
+ const toggleUrl = sidebar.dataset.toggleUrl;
18
+ const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
19
+ let isSessionCollapsed = sidebar.dataset.sessionCollapsed === "true";
20
+
21
+ // Function to handle sidebar collapsing based on window size
22
+ function adjustSidebarForWindowSize() {
23
+ const screenWidth = window.innerWidth;
24
+ const titlebar = document.querySelector('.titlebar');
25
+ const titlebarHeight = titlebar ? titlebar.offsetHeight : 0;
26
+
27
+ if (screenWidth < 1100) {
28
+ // Always collapse sidebar on small screens
29
+ sidebar.classList.add("collapsed");
30
+ initializeTooltips();
31
+
32
+ // Dynamic positioning to anchor to titlebar
33
+ sidebar.style.top = titlebarHeight + 'px';
34
+ sidebar.style.height = (window.innerHeight - titlebarHeight) + 'px';
35
+ } else {
36
+ // Reset styles for large screens to let CSS take over (sticky)
37
+ sidebar.style.top = '';
38
+ sidebar.style.height = '';
39
+
40
+ // Use session state for larger screens
41
+ if (isSessionCollapsed) {
42
+ sidebar.classList.add("collapsed");
43
+ initializeTooltips();
44
+ } else {
45
+ sidebar.classList.remove("collapsed");
46
+ deinitializeTooltips();
47
+ }
48
+ }
49
+ }
50
+
51
+ // Adjust sidebar state on load
52
+ adjustSidebarForWindowSize();
53
+
54
+ // Check if sidebar is collapsed on load and initialize tooltips if so
55
+ if (sidebar.classList.contains("collapsed")) {
56
+ initializeTooltips();
57
+ }
58
+
59
+ // Listen for window resize
60
+ window.addEventListener("resize", adjustSidebarForWindowSize);
61
+
62
+ // Toggle sidebar and update session via AJAX
63
+ if (sidebarToggle) {
64
+ sidebarToggle.addEventListener("click", function () {
65
+ sidebar.classList.toggle("collapsed");
66
+ const isCollapsed = sidebar.classList.contains("collapsed");
67
+
68
+ // Update tooltips immediately for all screen sizes
69
+ if (isCollapsed) {
70
+ initializeTooltips();
71
+ } else {
72
+ deinitializeTooltips();
73
+ }
74
+
75
+ // Only update session if screen width is >= 1100px
76
+ if (window.innerWidth >= 1100) {
77
+ fetch(toggleUrl, {
78
+ method: "POST",
79
+ headers: {
80
+ "X-CSRFToken": csrfToken,
81
+ "Content-Type": "application/x-www-form-urlencoded",
82
+ },
83
+ body: `collapsed=${isCollapsed}`
84
+ }).then(response => response.json())
85
+ .then(data => {
86
+ if (data.status === "success") {
87
+ isSessionCollapsed = isCollapsed; // Update local state
88
+ }
89
+ }).catch(error => console.error("Error updating sidebar state:", error));
90
+ }
91
+
92
+ setTimeout(triggerAutoscale, 250);
93
+ });
94
+ }
95
+ });
96
+
97
+ // Close sidebar when clicking outside (only for small screens)
98
+ document.addEventListener("click", function (event) {
99
+ const sidebar = document.getElementById("sidebar");
100
+ const sidebarToggle = document.getElementById("sidebarToggle");
101
+ const screenWidth = window.innerWidth;
102
+
103
+ if (sidebar && sidebarToggle && screenWidth < 1100 && !sidebar.contains(event.target) && !sidebarToggle.contains(event.target)) {
104
+ if (!sidebar.classList.contains("collapsed")) {
105
+ sidebar.classList.add("collapsed");
106
+
107
+ const toggleUrl = sidebar.dataset.toggleUrl;
108
+ const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
109
+
110
+ fetch(toggleUrl, {
111
+ method: "POST",
112
+ headers: {
113
+ "X-CSRFToken": csrfToken,
114
+ "Content-Type": "application/x-www-form-urlencoded",
115
+ },
116
+ body: "collapsed=true"
117
+ }).then(response => response.json())
118
+ .catch(error => console.error("Error updating sidebar state:", error));
119
+
120
+ initializeTooltips();
121
+ }
122
+ }
123
+ });
124
+
125
+ function initializeTooltips() {
126
+ const sidebarItems = document.querySelectorAll(".sidebar.collapsed .list-group-item, .sidebar.collapsed .accordion-button");
127
+ sidebarItems.forEach(item => {
128
+ if (item._tooltip) {
129
+ item._tooltip.dispose();
130
+ }
131
+ item._tooltip = new bootstrap.Tooltip(item, {
132
+ title: item.querySelector("span").textContent,
133
+ placement: "right",
134
+ customClass: "tooltip-custom"
135
+ });
136
+ });
137
+ }
138
+
139
+ function deinitializeTooltips() {
140
+ const sidebarItems = document.querySelectorAll(".sidebar .list-group-item, .sidebar .accordion-button");
141
+ sidebarItems.forEach(item => {
142
+ if (item._tooltip) {
143
+ item._tooltip.dispose();
144
+ delete item._tooltip;
145
+ }
146
+ });
147
+ }
148
+
149
+ function triggerAutoscale() {
150
+ if (window.innerWidth > 1100) {
151
+ const autoscaleButton = document.querySelector('.modebar-btn[data-title="Reset axes"]');
152
+ if (autoscaleButton) {
153
+ autoscaleButton.click();
154
+ }
155
+ }
156
+ }
@@ -0,0 +1,93 @@
1
+
2
+ .sidebar {
3
+ z-index: 900;
4
+ transition: width 0.15s ease-in-out;
5
+ background-color: white;
6
+ position: -webkit-sticky; /* For Safari */
7
+ position: sticky;
8
+ top: 0; /* Sticks the sidebar at the top of the parent container */
9
+ overflow-y: auto;
10
+ overflow-x: hidden;
11
+ height: calc(100vh - 45px); /* Fallback/Base height for sticky behavior */
12
+ /* Custom Scrollbar for Webkit */
13
+ scrollbar-width: thin;
14
+ scrollbar-color: rgba(0,0,0,0.2) transparent;
15
+ }
16
+
17
+ .sidebar::-webkit-scrollbar {
18
+ width: 6px;
19
+ }
20
+ .sidebar::-webkit-scrollbar-track {
21
+ background: transparent;
22
+ }
23
+ .sidebar::-webkit-scrollbar-thumb {
24
+ background-color: rgba(0,0,0,0.2);
25
+ border-radius: 20px;
26
+ }
27
+
28
+ .sidebar.collapsed {
29
+ width: 52px !important;
30
+ }
31
+ .sidebar.collapsed .list-group-item span {
32
+ display: none !important;
33
+ }
34
+ .sidebar.collapsed .accordion-button span {
35
+ display: none !important;
36
+ }
37
+ .sidebar.collapsed .accordion-button::after {
38
+ background-image: none;
39
+ }
40
+
41
+
42
+ .sidebar-ghost {
43
+ display: none;
44
+ width: 52px;
45
+ flex-shrink: 0;
46
+ }
47
+
48
+ @media (max-width: 1100px) {
49
+ .sidebar-ghost {
50
+ display: block;
51
+ }
52
+
53
+ .sidebar {
54
+ position: fixed;
55
+ top: 45px; /* Fallback, JS calculates exact height */
56
+ right: 0;
57
+ width: 250px;
58
+ transition: width 0.15s ease-in-out;
59
+ height: calc(100vh - 45px); /* Fallback */
60
+ z-index: 1000;
61
+ }
62
+
63
+ .sidebar.collapsed {
64
+ width: 52px !important;
65
+ right: 0;
66
+ position: fixed;
67
+ }
68
+ }
69
+ .sidebar .list-group-item {
70
+ border: none !important;
71
+ font-size: 18px !important;
72
+ font-weight: 600 !important;
73
+ height: 60px !important;
74
+ white-space: nowrap;
75
+ overflow: hidden;
76
+ padding: 12px;
77
+ align-items: center;
78
+ }
79
+
80
+ .sidebar .accordion-button {
81
+ font-size: 18px !important;
82
+ font-weight: 600 !important;
83
+ border: none !important;
84
+ height: 60px !important;
85
+ white-space: nowrap;
86
+ overflow: hidden;
87
+ padding: 12px;
88
+ align-items: center;
89
+ }
90
+
91
+ .sidebar .accordion-button:not(.collapsed) {
92
+ background-color: var(--body) !important;
93
+ }
@@ -0,0 +1,31 @@
1
+ {% load static %}
2
+ <link rel="stylesheet" href="{% static 'sidebar/style.css' %}">
3
+ <script src="{% static 'sidebar/js/sidebar.js' %}" nonce="{{ request.csp_nonce }}" defer></script>
4
+ <!-- Ghost Sidebar for small screens layout stability -->
5
+ <div class="sidebar-ghost"></div>
6
+
7
+ <!-- sidebar.html -->
8
+ <div class="col-2 flex-column shadow-sm sidebar {% if request.session.sidebarCollapsed %}collapsed{% endif %} no-print"
9
+ id="sidebar"
10
+ data-toggle-url="{% url 'toggle_sidebar' %}"
11
+ data-session-collapsed="{{ request.session.sidebarCollapsed|yesno:'true,false' }}">
12
+ <div class="list-group flex-shrink-0">
13
+
14
+ <!-- DEFAULT CONTENT / INSTRUCTIONS -->
15
+ <div class="p-3 text-center text-muted">
16
+ <i class="bi bi-info-circle mb-2" style="font-size: 24px;"></i>
17
+ <p class="small">
18
+ <strong>Default Sidebar</strong><br>
19
+ To customize this menu, create a file at:<br>
20
+ <code>templates/sidebar/content.html</code><br>
21
+ in your Django project.
22
+ </p>
23
+ </div>
24
+
25
+ <a href="#" class="list-group-item list-group-item-action">
26
+ <i class="bi bi-house me-2" style="font-size: 24px;"></i>
27
+ <span>Example Home</span>
28
+ </a>
29
+
30
+ </div>
31
+ </div>
sidebar/urls.py ADDED
@@ -0,0 +1,6 @@
1
+ from django.urls import path
2
+ from . import views
3
+
4
+ urlpatterns = [
5
+ path("toggle-sidebar/", views.toggle_sidebar, name="toggle_sidebar"),
6
+ ]
sidebar/views.py ADDED
@@ -0,0 +1,9 @@
1
+ from django.http import JsonResponse
2
+
3
+ # Helper Function that handles the sidebar toggle and state
4
+ def toggle_sidebar(request):
5
+ if request.method == "POST" and request.user.is_authenticated:
6
+ collapsed = request.POST.get("collapsed") == "true"
7
+ request.session["sidebarCollapsed"] = collapsed
8
+ return JsonResponse({"status": "success"})
9
+ return JsonResponse({"status": "error"}, status=400)