micro-sidebar 1.0.2__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,111 @@
1
+ Metadata-Version: 2.4
2
+ Name: micro_sidebar
3
+ Version: 1.0.2
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 RTL Django sidebar app for Web Apps.
32
+
33
+ ## Requirements
34
+
35
+ - **Django**: >= 5.1
36
+ - **Bootstrap**: 5 (Required for consistent styling)
37
+
38
+ ## Installation
39
+
40
+ 1. **Install the package:**
41
+ ```bash
42
+ pip install micro-sidebar
43
+ ```
44
+
45
+ 2. **Add to `INSTALLED_APPS`:**
46
+ In your `settings.py`:
47
+ ```python
48
+ INSTALLED_APPS = [
49
+ ...
50
+ 'sidebar',
51
+ ...
52
+ ]
53
+ ```
54
+
55
+ 3. **Configure URLs:**
56
+ In your project's `urls.py`:
57
+ ```python
58
+ from django.urls import path, include
59
+
60
+ urlpatterns = [
61
+ ...
62
+ path('sidebar/', include('sidebar.urls')),
63
+ ...
64
+ ]
65
+ ```
66
+
67
+ 4. **Add to your Base Template:**
68
+ In your `base.html` (or equivalent), include the sidebar. It is designed to sit to the right of your main content.
69
+
70
+ Example structure using Flexbox:
71
+ ```html
72
+ <body>
73
+ <div class="d-flex">
74
+ <!-- Sidebar -->
75
+ {% include "sidebar/content.html" %}
76
+
77
+ <!-- Main Content -->
78
+ <div class="flex-grow-1">
79
+ {% block content %}{% endblock %}
80
+ </div>
81
+ </div>
82
+
83
+ <!-- Bootstrap JS (Required) -->
84
+ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
85
+ </body>
86
+ ```
87
+
88
+ ## Customization
89
+
90
+ ### Overriding Content
91
+ The sidebar comes with a default template. To customize the links and content, create a file named `content.html` inside `templates/sidebar/` in your project's `templates` directory.
92
+
93
+ **Path:** `your_project/templates/sidebar/content.html`
94
+
95
+ The default sidebar logic expects specific classes like `.list-group-item` and `.accordion-item` for the collapsible features to work correctly with the provided JS.
96
+
97
+ ### Positioning
98
+ The sidebar is sticky by default. If your app has a top navigation bar (titlebar), the sidebar will automatically adjust its position below it on small screens. If no titlebar is detected, it will stick to the top of the viewport.
99
+
100
+ ## RTL / LTR Support
101
+ This sidebar is primarily designed for **RTL (Right-to-Left)** interfaces (e.g., Arabic).
102
+ While it may theoretically work in LTR environments if standard Bootstrap files are used instead of RTL versions, this has **not been fully tested**.
103
+ > *Future updates are planned to support dynamic language switching between RTL and LTR.*
104
+
105
+ ## Version History
106
+
107
+ | Version | Changes |
108
+ | :--- | :--- |
109
+ | **v1.0.0** | Initial Release. |
110
+ | **v1.0.1** | Fixed titlebar positioning bug causing overlap/gaps. |
111
+ | **v1.0.2** | Improved documentation clarity and added usage instructions. |
@@ -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=jLspMcoRDpsw_KpIegGwSL-FRi_uz_8pQ95zDXMnTZ0,6221
8
+ sidebar/templates/sidebar/content.html,sha256=hSqgzvuWZRxlUN1-zC-Oovppd_rbnCylxQmfRdGIbhY,1298
9
+ micro_sidebar-1.0.2.dist-info/METADATA,sha256=fctkEC7TQqt0tDm2oAQzXalyb058WNMLWtoLA-gwJH4,3607
10
+ micro_sidebar-1.0.2.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
11
+ micro_sidebar-1.0.2.dist-info/top_level.txt,sha256=ih69sjMhU1wOB9HzUV90yEY98aiPuGhzPBBBE-YtJ3w,8
12
+ micro_sidebar-1.0.2.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,163 @@
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
+ // Dynamic positioning to anchor to titlebar
34
+ if (titlebarHeight > 0) {
35
+ sidebar.style.top = titlebarHeight + 'px';
36
+ sidebar.style.height = (window.innerHeight - titlebarHeight) + 'px';
37
+ } else {
38
+ // Failsafe: Stick to top if no titlebar
39
+ sidebar.style.top = '0px';
40
+ sidebar.style.height = '100vh';
41
+ }
42
+ } else {
43
+ // Reset styles for large screens to let CSS take over (sticky)
44
+ sidebar.style.top = '';
45
+ sidebar.style.height = '';
46
+
47
+ // Use session state for larger screens
48
+ if (isSessionCollapsed) {
49
+ sidebar.classList.add("collapsed");
50
+ initializeTooltips();
51
+ } else {
52
+ sidebar.classList.remove("collapsed");
53
+ deinitializeTooltips();
54
+ }
55
+ }
56
+ }
57
+
58
+ // Adjust sidebar state on load
59
+ adjustSidebarForWindowSize();
60
+
61
+ // Check if sidebar is collapsed on load and initialize tooltips if so
62
+ if (sidebar.classList.contains("collapsed")) {
63
+ initializeTooltips();
64
+ }
65
+
66
+ // Listen for window resize
67
+ window.addEventListener("resize", adjustSidebarForWindowSize);
68
+
69
+ // Toggle sidebar and update session via AJAX
70
+ if (sidebarToggle) {
71
+ sidebarToggle.addEventListener("click", function () {
72
+ sidebar.classList.toggle("collapsed");
73
+ const isCollapsed = sidebar.classList.contains("collapsed");
74
+
75
+ // Update tooltips immediately for all screen sizes
76
+ if (isCollapsed) {
77
+ initializeTooltips();
78
+ } else {
79
+ deinitializeTooltips();
80
+ }
81
+
82
+ // Only update session if screen width is >= 1100px
83
+ if (window.innerWidth >= 1100) {
84
+ fetch(toggleUrl, {
85
+ method: "POST",
86
+ headers: {
87
+ "X-CSRFToken": csrfToken,
88
+ "Content-Type": "application/x-www-form-urlencoded",
89
+ },
90
+ body: `collapsed=${isCollapsed}`
91
+ }).then(response => response.json())
92
+ .then(data => {
93
+ if (data.status === "success") {
94
+ isSessionCollapsed = isCollapsed; // Update local state
95
+ }
96
+ }).catch(error => console.error("Error updating sidebar state:", error));
97
+ }
98
+
99
+ setTimeout(triggerAutoscale, 250);
100
+ });
101
+ }
102
+ });
103
+
104
+ // Close sidebar when clicking outside (only for small screens)
105
+ document.addEventListener("click", function (event) {
106
+ const sidebar = document.getElementById("sidebar");
107
+ const sidebarToggle = document.getElementById("sidebarToggle");
108
+ const screenWidth = window.innerWidth;
109
+
110
+ if (sidebar && sidebarToggle && screenWidth < 1100 && !sidebar.contains(event.target) && !sidebarToggle.contains(event.target)) {
111
+ if (!sidebar.classList.contains("collapsed")) {
112
+ sidebar.classList.add("collapsed");
113
+
114
+ const toggleUrl = sidebar.dataset.toggleUrl;
115
+ const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
116
+
117
+ fetch(toggleUrl, {
118
+ method: "POST",
119
+ headers: {
120
+ "X-CSRFToken": csrfToken,
121
+ "Content-Type": "application/x-www-form-urlencoded",
122
+ },
123
+ body: "collapsed=true"
124
+ }).then(response => response.json())
125
+ .catch(error => console.error("Error updating sidebar state:", error));
126
+
127
+ initializeTooltips();
128
+ }
129
+ }
130
+ });
131
+
132
+ function initializeTooltips() {
133
+ const sidebarItems = document.querySelectorAll(".sidebar.collapsed .list-group-item, .sidebar.collapsed .accordion-button");
134
+ sidebarItems.forEach(item => {
135
+ if (item._tooltip) {
136
+ item._tooltip.dispose();
137
+ }
138
+ item._tooltip = new bootstrap.Tooltip(item, {
139
+ title: item.querySelector("span").textContent,
140
+ placement: "right",
141
+ customClass: "tooltip-custom"
142
+ });
143
+ });
144
+ }
145
+
146
+ function deinitializeTooltips() {
147
+ const sidebarItems = document.querySelectorAll(".sidebar .list-group-item, .sidebar .accordion-button");
148
+ sidebarItems.forEach(item => {
149
+ if (item._tooltip) {
150
+ item._tooltip.dispose();
151
+ delete item._tooltip;
152
+ }
153
+ });
154
+ }
155
+
156
+ function triggerAutoscale() {
157
+ if (window.innerWidth > 1100) {
158
+ const autoscaleButton = document.querySelector('.modebar-btn[data-title="Reset axes"]');
159
+ if (autoscaleButton) {
160
+ autoscaleButton.click();
161
+ }
162
+ }
163
+ }
@@ -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)