micro-sidebar 1.2.2__py3-none-any.whl → 2.2.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,41 @@
1
+ document.addEventListener('DOMContentLoaded', function() {
2
+ const root = document.documentElement;
3
+ const themes = ['light', 'blue', 'gold', 'green', 'red', 'dark'];
4
+
5
+ // Load saved theme
6
+ const savedTheme = localStorage.getItem('appTheme');
7
+ if (savedTheme && themes.includes(savedTheme)) {
8
+ root.classList.add(`theme-${savedTheme}`);
9
+ }
10
+
11
+ // Global function to set theme
12
+ window.setTheme = function(theme) {
13
+ // Remove all current theme classes
14
+ themes.forEach(t => root.classList.remove(`theme-${t}`));
15
+
16
+ if (theme && themes.includes(theme)) {
17
+ root.classList.add(`theme-${theme}`);
18
+ localStorage.setItem('appTheme', theme);
19
+ } else {
20
+ localStorage.removeItem('appTheme');
21
+ }
22
+
23
+ // Visual Update: Highlight active theme circle
24
+ updateActiveThemeUI(theme || 'light');
25
+
26
+ // Dispatch event for components that might need resizing (like Plotly)
27
+ window.dispatchEvent(new Event('resize'));
28
+ };
29
+
30
+ function updateActiveThemeUI(activeTheme) {
31
+ document.querySelectorAll('.theme-preview').forEach(el => {
32
+ el.classList.remove('active');
33
+ if (el.getAttribute('data-theme') === activeTheme) {
34
+ el.classList.add('active');
35
+ }
36
+ });
37
+ }
38
+
39
+ // Initialize UI on load
40
+ updateActiveThemeUI(savedTheme || 'light');
41
+ });
@@ -0,0 +1,51 @@
1
+ /* --- Red Theme (Bordeaux) --- */
2
+ :root.theme-red {
3
+ --title: #7f1d1d;
4
+ --body: #fef2f28b;
5
+ --htitle: #991b1b;
6
+ --hbody: #fee2e2db;
7
+ --table-row: #fdeded;
8
+ --table-row-hover: #fcdbdb;
9
+ --primal: #ef4444;
10
+ --primal_dark: #dc2626;
11
+ --primal-rgb: 239, 68, 68;
12
+ --btn-primary-shadow: rgba(239, 68, 68, 0.4);
13
+
14
+ /* Login Theme Variables */
15
+ --bg-gradient: linear-gradient(135deg, #fef2f2 0%, #fee2e2 100%);
16
+ --right-bg: var(--title);
17
+ --primary-color: var(--primal);
18
+
19
+ /* Bootstrap Overrides */
20
+ --bs-primary: var(--primal);
21
+ --bs-primary-rgb: var(--primal-rgb);
22
+ --bs-btn-bg: var(--primal);
23
+ --bs-btn-border-color: var(--primal);
24
+ --bs-btn-hover-bg: var(--primal_dark);
25
+ --bs-btn-hover-border-color: var(--primal_dark);
26
+ --bs-link-color: var(--primal);
27
+ --bs-link-hover-color: var(--primal_dark);
28
+ }
29
+ :root.theme-red .titlebar {
30
+ background: linear-gradient(90deg, #ffffff 10%, #fdeded 90%) !important;
31
+ }
32
+ :root.theme-red #sidebar {
33
+ background: linear-gradient(180deg, #ffffff 10%, #fdeded 100%) !important;
34
+ border-left: 1px solid #fcdbdb !important;
35
+ }
36
+
37
+ :root.theme-red .page .right {
38
+ background: var(--primal_dark) !important; /* Darker logo container */
39
+ box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.3) !important;
40
+ color: #e2e8f0 !important;
41
+ }
42
+
43
+ /* Red Overrides */
44
+ :root.theme-red .dropdown-item:hover {
45
+ background-color: rgba(239, 68, 68, 0.08) !important;
46
+ color: #dc2626 !important;
47
+ }
48
+ :root.theme-red .titlebar .btn-light:hover {
49
+ background-color: rgba(239, 68, 68, 0.08) !important;
50
+ color: #dc2626 !important;
51
+ }
@@ -0,0 +1,15 @@
1
+ <div class="sidebar-auto-items" id="sidebarAutoItems">
2
+ {% for item in items %}
3
+ <a href="{{ item.url }}"
4
+ class="list-group-item list-group-item-action{% if item.active %} active{% endif %}"
5
+ data-url-name="{{ item.url_name }}">
6
+ <i class="bi {{ item.icon }} me-2" style="font-size: 24px;"></i>
7
+ <span>{{ item.label }}</span>
8
+ </a>
9
+ {% empty %}
10
+ <div class="p-3 text-center text-muted small">
11
+ <i class="bi bi-inbox mb-2" style="font-size: 24px;"></i>
12
+ <p class="mb-0">لا توجد عناصر</p>
13
+ </div>
14
+ {% endfor %}
15
+ </div>
@@ -0,0 +1,34 @@
1
+ {% if groups %}
2
+ <div class="accordion accordion-flush" id="sidebarExtraAccordion">
3
+ {% for group_name, group in groups.items %}
4
+ <div class="accordion-item">
5
+ <h2 class="accordion-header">
6
+ <button class="accordion-button{% if not group.has_active %} collapsed{% endif %}"
7
+ type="button"
8
+ data-bs-toggle="collapse"
9
+ data-bs-target="#extraGroup{{ forloop.counter }}"
10
+ aria-expanded="{% if group.has_active %}true{% else %}false{% endif %}"
11
+ aria-controls="extraGroup{{ forloop.counter }}">
12
+ <i class="bi {{ group.icon }} me-2" style="font-size: 24px;"></i>
13
+ <span>{{ group_name }}</span>
14
+ </button>
15
+ </h2>
16
+ <div id="extraGroup{{ forloop.counter }}"
17
+ class="accordion-collapse collapse{% if group.has_active %} show{% endif %}"
18
+ data-bs-parent="#sidebarExtraAccordion">
19
+ <div class="accordion-body p-0" data-group-name="{{ group_name|slugify }}">
20
+ {% for item in group.items %}
21
+ <a href="{{ item.url }}"
22
+ class="list-group-item list-group-item-action{% if item.active %} active{% endif %}"
23
+ data-url-name="{{ item.url_name }}">
24
+ <i class="bi {{ item.icon }} me-2" style="font-size: 24px;"></i>
25
+ <span>{{ item.label }}</span>
26
+ </a>
27
+ {% endfor %}
28
+ </div>
29
+ </div>
30
+ </div>
31
+ {% endfor %}
32
+ </div>
33
+ {% endif %}
34
+
@@ -1,8 +1,15 @@
1
1
  {% load static %}
2
2
  <link rel="stylesheet" href="{% static 'sidebar/sidebar.css' %}">
3
+ <link rel="stylesheet" href="{% static 'sidebar/css/theme_picker.css' %}">
4
+ <link rel="stylesheet" href="{% static 'sidebar/css/reorder.css' %}">
5
+ <link rel="stylesheet" href="{% static 'themes/main.css' %}">
3
6
  <script src="{% static 'sidebar/sidebar.js' %}" nonce="{{ request.csp_nonce }}" defer></script>
7
+ <script src="{% static 'themes/main.js' %}" nonce="{{ request.csp_nonce }}" defer></script>
8
+ <script src="{% static 'sidebar/js/theme_picker.js' %}" nonce="{{ request.csp_nonce }}" defer></script>
9
+ <script src="{% static 'sidebar/js/reorder.js' %}" nonce="{{ request.csp_nonce }}" defer></script>
4
10
  <!-- Ghost Sidebar for small screens layout stability -->
5
- <div class="sidebar-ghost"></div>
11
+ {% if request.user.is_authenticated %}
12
+ <!-- <div class="sidebar-ghost"></div> -->
6
13
 
7
14
  <!-- sidebar.html -->
8
15
  <div class="col-2 flex-column shadow-sm sidebar {% if request.session.sidebarCollapsed %}collapsed{% endif %} no-print"
@@ -26,7 +33,7 @@
26
33
  {% block items %}
27
34
 
28
35
  <!-- DEFAULT CONTENT / INSTRUCTIONS -->
29
- <div class="p-3 text-center text-muted">
36
+ <!-- <div class="p-3 text-center text-muted">
30
37
  <i class="bi bi-info-circle mb-2" style="font-size: 24px;"></i>
31
38
  <p class="small">
32
39
  <strong>Default Sidebar</strong><br>
@@ -34,13 +41,95 @@
34
41
  <code>sidebar/main.html</code><br>
35
42
  and override the <code>items</code> block.
36
43
  </p>
37
- </div>
44
+ </div> -->
38
45
 
39
- <a href="#" class="list-group-item list-group-item-action">
46
+ <!-- <a href="#" class="list-group-item list-group-item-action">
40
47
  <i class="bi bi-house me-2" style="font-size: 24px;"></i>
41
48
  <span>Example Home</span>
42
- </a>
49
+ </a> -->
43
50
 
44
51
  {% endblock %}
45
52
  </div>
53
+ <script nonce="{{ request.csp_nonce }}">
54
+ // Immediate order restore to prevent FOUC - runs before browser paint
55
+ (function() {
56
+ var STORAGE_KEY_AUTO = 'sidebar_auto_order';
57
+ var STORAGE_KEY_PREFIX_EXTRA = 'sidebar_extra_';
58
+
59
+ function restoreContainer(container, storageKey) {
60
+ var saved;
61
+ try {
62
+ saved = localStorage.getItem(storageKey);
63
+ if (!saved) return;
64
+ saved = JSON.parse(saved);
65
+ } catch(e) { return; }
66
+ if (!Array.isArray(saved) || saved.length === 0) return;
67
+
68
+ var items = container.querySelectorAll(':scope > .list-group-item[data-url-name]');
69
+ var itemMap = {};
70
+ for (var i = 0; i < items.length; i++) {
71
+ itemMap[items[i].dataset.urlName] = items[i];
72
+ }
73
+ for (var j = 0; j < saved.length; j++) {
74
+ var item = itemMap[saved[j]];
75
+ if (item) {
76
+ container.appendChild(item);
77
+ delete itemMap[saved[j]];
78
+ }
79
+ }
80
+ for (var key in itemMap) {
81
+ container.appendChild(itemMap[key]);
82
+ }
83
+ }
84
+
85
+ function slugify(text) {
86
+ return text.toString().toLowerCase().trim()
87
+ .replace(/\s+/g, '-').replace(/[^\w\-]+/g, '').replace(/\-\-+/g, '-');
88
+ }
89
+
90
+ // Restore auto items
91
+ var autoContainer = document.getElementById('sidebarAutoItems');
92
+ if (autoContainer) {
93
+ restoreContainer(autoContainer, STORAGE_KEY_AUTO);
94
+ }
95
+
96
+ // Restore extra groups
97
+ var accordionBodies = document.querySelectorAll('.sidebar .accordion-body');
98
+ for (var k = 0; k < accordionBodies.length; k++) {
99
+ var body = accordionBodies[k];
100
+ var groupName = body.dataset.groupName;
101
+ if (!groupName) {
102
+ var btn = body.closest('.accordion-item');
103
+ if (btn) {
104
+ var span = btn.querySelector('.accordion-button span');
105
+ if (span) groupName = span.textContent.trim();
106
+ }
107
+ }
108
+ if (groupName) {
109
+ restoreContainer(body, STORAGE_KEY_PREFIX_EXTRA + slugify(groupName));
110
+ }
111
+ }
112
+
113
+ window._sidebarOrderRestored = true;
114
+ })();
115
+ </script>
116
+
117
+ <!-- Sidebar Toolbar (Theme Picker, Reorder, etc.) -->
118
+ <div class="sidebar-toolbar no-print">
119
+ <i class="bi bi-arrows-move reorder-toggle" id="sidebarReorderToggle" title="إعادة الترتيب"></i>
120
+ <i class="bi bi-chevron-up theme-arrow" id="sidebarThemeArrow"></i>
121
+ <div class="current-theme-indicator" id="sidebarThemeIndicator" title="تغيير المظهر"></div>
122
+ <div class="theme-popup" id="sidebarThemePopup">
123
+ <div class="small fw-bold mb-2 text-center border-bottom pb-1">اختر اللون</div>
124
+ <div class="theme-options-grid">
125
+ <div class="theme-option-circle theme-circle-light" data-theme="light" title="أبيض"></div>
126
+ <div class="theme-option-circle theme-circle-blue" data-theme="blue" title="ملكي"></div>
127
+ <div class="theme-option-circle theme-circle-gold" data-theme="gold" title="ذهبي"></div>
128
+ <div class="theme-option-circle theme-circle-green" data-theme="green" title="أخضر"></div>
129
+ <div class="theme-option-circle theme-circle-red" data-theme="red" title="أحمر"></div>
130
+ <div class="theme-option-circle theme-circle-dark" data-theme="dark" title="ليلي"></div>
131
+ </div>
132
+ </div>
133
+ </div>
46
134
  </div>
135
+ {% endif %}
@@ -0,0 +1 @@
1
+ # This file makes templatetags a Python package
@@ -0,0 +1,74 @@
1
+ """
2
+ Template tags for the sidebar app.
3
+
4
+ Provides the {% auto_sidebar %} tag for rendering auto-discovered
5
+ navigation items.
6
+ """
7
+ from django import template
8
+ from django.urls import reverse, NoReverseMatch
9
+
10
+ register = template.Library()
11
+
12
+
13
+ @register.inclusion_tag('sidebar/auto.html', takes_context=True)
14
+ def auto_sidebar(context):
15
+ """
16
+ Render auto-discovered sidebar items.
17
+
18
+ Uses items from sidebar_auto_items context variable (provided by
19
+ the context processor) and adds resolved URLs and active state.
20
+
21
+ Usage:
22
+ {% load sidebar_tags %}
23
+ {% auto_sidebar %}
24
+ """
25
+ request = context.get('request')
26
+ items = list(context.get('sidebar_auto_items', []))
27
+
28
+ # Add resolved URLs and active state
29
+ for item in items:
30
+ try:
31
+ item['url'] = reverse(item['url_name'])
32
+ # Check if current path matches or starts with this URL
33
+ item['active'] = request.path == item['url'] or request.path.startswith(item['url'].rstrip('/') + '/')
34
+ except NoReverseMatch:
35
+ item['url'] = '#'
36
+ item['active'] = False
37
+
38
+ return {'items': items, 'request': request}
39
+
40
+
41
+ @register.simple_tag(takes_context=True)
42
+ def sidebar_item_class(context, url_name):
43
+ """
44
+ Return 'active' class if current path matches the given URL name.
45
+
46
+ Usage:
47
+ <a href="{% url 'decree_list' %}" class="list-group-item {% sidebar_item_class 'decree_list' %}">
48
+ """
49
+ request = context.get('request')
50
+ try:
51
+ url = reverse(url_name)
52
+ if request.path == url or request.path.startswith(url.rstrip('/') + '/'):
53
+ return 'active'
54
+ except NoReverseMatch:
55
+ pass
56
+ return ''
57
+
58
+
59
+ @register.inclusion_tag('sidebar/extra_groups.html', takes_context=True)
60
+ def extra_sidebar(context):
61
+ """
62
+ Render extra sidebar items grouped in accordions.
63
+
64
+ Uses sidebar_extra_groups from context (provided by context processor).
65
+ Groups are rendered as Bootstrap accordions at the end of the sidebar.
66
+
67
+ Usage:
68
+ {% load sidebar_tags %}
69
+ {% extra_sidebar %}
70
+ """
71
+ groups = context.get('sidebar_extra_groups', {})
72
+ request = context.get('request')
73
+ return {'groups': groups, 'request': request}
74
+
@@ -1,13 +0,0 @@
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/sidebar.css,sha256=f6xLSC3JiZFABkxWlFesuzAZZtAT3WW1nZBFMDQNS6Y,5283
7
- sidebar/static/sidebar/sidebar.js,sha256=xDp038tlscz5KeTjBiEQTzZ2T7a8k4NY3rC6e9NvMsM,6314
8
- sidebar/templates/sidebar/main.html,sha256=zp_ENJberDGhPERxnzDMwLsjwr5_uTAKd3UpDFNUW-k,1944
9
- micro_sidebar-1.2.2.dist-info/LICENSE,sha256=Fco89ULLSSxKkC2KKnx57SaT0R7WOkZfuk8IYcGiN50,1063
10
- micro_sidebar-1.2.2.dist-info/METADATA,sha256=35UC3_f-k_PQ1mh1c3FIyUn5-ORTgJQKU72tJqKc320,4431
11
- micro_sidebar-1.2.2.dist-info/WHEEL,sha256=pkctZYzUS4AYVn6dJ-7367OJZivF2e8RA9b_ZBjif18,92
12
- micro_sidebar-1.2.2.dist-info/top_level.txt,sha256=ih69sjMhU1wOB9HzUV90yEY98aiPuGhzPBBBE-YtJ3w,8
13
- micro_sidebar-1.2.2.dist-info/RECORD,,