adminator-admin-dashboard 2.7.0 → 2.7.1

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,388 @@
1
+ /**
2
+ * Modern Sidebar Component with TypeScript
3
+ * Replaces jQuery-based sidebar functionality with vanilla JavaScript
4
+ */
5
+
6
+ import type { ComponentInterface, SidebarOptions, SidebarState, AnimationOptions } from '../../../types';
7
+
8
+ export interface SidebarEventDetail {
9
+ collapsed: boolean;
10
+ }
11
+
12
+ export interface SidebarToggleEvent extends CustomEvent {
13
+ detail: SidebarEventDetail;
14
+ }
15
+
16
+ declare global {
17
+ interface Window {
18
+ EVENT?: Event;
19
+ }
20
+ }
21
+
22
+ export class Sidebar implements ComponentInterface {
23
+ public name: string = 'Sidebar';
24
+ public element: HTMLElement;
25
+ public options: SidebarOptions;
26
+ public isInitialized: boolean = false;
27
+
28
+ private sidebar: HTMLElement | null;
29
+ private sidebarMenu: HTMLElement | null;
30
+ private sidebarToggleLinks: NodeListOf<HTMLAnchorElement>;
31
+ private sidebarToggleById: HTMLElement | null;
32
+ private app: HTMLElement | null;
33
+ private state: SidebarState;
34
+
35
+ constructor(element?: HTMLElement, options: SidebarOptions = {}) {
36
+ this.element = element || document.body;
37
+ this.options = {
38
+ breakpoint: 768,
39
+ collapsible: true,
40
+ autoHide: true,
41
+ animation: true,
42
+ animationDuration: 200,
43
+ ...options,
44
+ };
45
+
46
+ this.sidebar = document.querySelector('.sidebar');
47
+ this.sidebarMenu = document.querySelector('.sidebar .sidebar-menu');
48
+ this.sidebarToggleLinks = document.querySelectorAll('.sidebar-toggle a');
49
+ this.sidebarToggleById = document.querySelector('#sidebar-toggle');
50
+ this.app = document.querySelector('.app');
51
+
52
+ this.state = {
53
+ isCollapsed: false,
54
+ isMobile: false,
55
+ activeMenu: null,
56
+ };
57
+
58
+ this.init();
59
+ }
60
+
61
+ /**
62
+ * Initialize the sidebar component
63
+ */
64
+ public init(): void {
65
+ if (!this.sidebar || !this.sidebarMenu) {
66
+ console.warn('Sidebar: Required elements not found');
67
+ return;
68
+ }
69
+
70
+ this.setupMenuToggle();
71
+ this.setupSidebarToggle();
72
+ this.setActiveLink();
73
+ this.handleResize();
74
+ this.setupEventListeners();
75
+
76
+ this.isInitialized = true;
77
+ }
78
+
79
+ /**
80
+ * Destroy the sidebar component
81
+ */
82
+ public destroy(): void {
83
+ this.removeEventListeners();
84
+ this.isInitialized = false;
85
+ }
86
+
87
+ /**
88
+ * Setup dropdown menu functionality
89
+ */
90
+ private setupMenuToggle(): void {
91
+ if (!this.sidebarMenu) return;
92
+
93
+ const menuLinks = this.sidebarMenu.querySelectorAll('li a');
94
+
95
+ menuLinks.forEach(link => {
96
+ link.addEventListener('click', this.handleMenuClick.bind(this));
97
+ });
98
+ }
99
+
100
+ /**
101
+ * Handle menu item click
102
+ */
103
+ private handleMenuClick(e: Event): void {
104
+ const link = e.target as HTMLAnchorElement;
105
+ const listItem = link.parentElement as HTMLLIElement;
106
+ const dropdownMenu = listItem?.querySelector('.dropdown-menu') as HTMLElement;
107
+
108
+ // If this is a regular navigation link (not dropdown), allow normal navigation
109
+ if (!dropdownMenu) {
110
+ return;
111
+ }
112
+
113
+ // Only prevent default for dropdown toggles
114
+ e.preventDefault();
115
+
116
+ if (listItem.classList.contains('open')) {
117
+ this.closeDropdown(listItem, dropdownMenu);
118
+ } else {
119
+ this.closeAllDropdowns();
120
+ this.openDropdown(listItem, dropdownMenu);
121
+ }
122
+ }
123
+
124
+ /**
125
+ * Open dropdown with smooth animation
126
+ */
127
+ private openDropdown(listItem: HTMLLIElement, dropdownMenu: HTMLElement): void {
128
+ listItem.classList.add('open');
129
+ dropdownMenu.style.display = 'block';
130
+ dropdownMenu.style.height = '0px';
131
+ dropdownMenu.style.overflow = 'hidden';
132
+
133
+ // Get the natural height
134
+ const height = dropdownMenu.scrollHeight;
135
+
136
+ // Animate to full height
137
+ const animation = dropdownMenu.animate([
138
+ { height: '0px' },
139
+ { height: `${height}px` },
140
+ ], {
141
+ duration: this.options.animationDuration,
142
+ easing: 'ease-out',
143
+ });
144
+
145
+ animation.onfinish = (): void => {
146
+ dropdownMenu.style.height = 'auto';
147
+ dropdownMenu.style.overflow = 'visible';
148
+ };
149
+ }
150
+
151
+ /**
152
+ * Close dropdown with smooth animation
153
+ */
154
+ private closeDropdown(listItem: HTMLLIElement, dropdownMenu: HTMLElement): void {
155
+ const height = dropdownMenu.scrollHeight;
156
+
157
+ dropdownMenu.style.height = `${height}px`;
158
+ dropdownMenu.style.overflow = 'hidden';
159
+
160
+ const animation = dropdownMenu.animate([
161
+ { height: `${height}px` },
162
+ { height: '0px' },
163
+ ], {
164
+ duration: this.options.animationDuration,
165
+ easing: 'ease-in',
166
+ });
167
+
168
+ animation.onfinish = (): void => {
169
+ listItem.classList.remove('open');
170
+ dropdownMenu.style.display = 'none';
171
+ dropdownMenu.style.height = '';
172
+ dropdownMenu.style.overflow = '';
173
+ };
174
+ }
175
+
176
+ /**
177
+ * Close all open dropdowns
178
+ */
179
+ private closeAllDropdowns(): void {
180
+ if (!this.sidebarMenu) return;
181
+
182
+ const openItems = this.sidebarMenu.querySelectorAll('li.open');
183
+
184
+ openItems.forEach(item => {
185
+ const dropdownMenu = item.querySelector('.dropdown-menu') as HTMLElement;
186
+ if (dropdownMenu) {
187
+ this.closeDropdown(item as HTMLLIElement, dropdownMenu);
188
+ }
189
+
190
+ // Also remove the has-active-child class
191
+ item.classList.remove('has-active-child');
192
+ });
193
+ }
194
+
195
+ /**
196
+ * Setup sidebar toggle functionality
197
+ */
198
+ private setupSidebarToggle(): void {
199
+ // Handle mobile sidebar toggle links (inside .sidebar-toggle divs)
200
+ this.sidebarToggleLinks.forEach(link => {
201
+ if (link && this.app) {
202
+ link.addEventListener('click', this.handleSidebarToggle.bind(this));
203
+ }
204
+ });
205
+
206
+ // Handle the main topbar sidebar toggle
207
+ if (this.sidebarToggleById && this.app) {
208
+ this.sidebarToggleById.addEventListener('click', this.handleSidebarToggle.bind(this));
209
+ }
210
+ }
211
+
212
+ /**
213
+ * Handle sidebar toggle click
214
+ */
215
+ private handleSidebarToggle(e: Event): void {
216
+ e.preventDefault();
217
+ this.toggleSidebar();
218
+ }
219
+
220
+ /**
221
+ * Toggle sidebar and handle resize events properly
222
+ */
223
+ private toggleSidebar(): void {
224
+ if (!this.app) return;
225
+
226
+ const wasCollapsed = this.state.isCollapsed;
227
+ this.state.isCollapsed = !wasCollapsed;
228
+
229
+ this.app.classList.toggle('is-collapsed');
230
+
231
+ // Dispatch custom event with proper typing
232
+ setTimeout(() => {
233
+ const event: SidebarToggleEvent = new CustomEvent('sidebar:toggle', {
234
+ detail: { collapsed: this.state.isCollapsed },
235
+ }) as SidebarToggleEvent;
236
+
237
+ window.dispatchEvent(event);
238
+
239
+ // Still trigger resize for masonry but with a specific check
240
+ if (window.EVENT) {
241
+ window.dispatchEvent(window.EVENT);
242
+ }
243
+ }, this.options.animationDuration || 300);
244
+ }
245
+
246
+ /**
247
+ * Set active link based on current URL
248
+ */
249
+ private setActiveLink(): void {
250
+ if (!this.sidebar) return;
251
+
252
+ // Remove active class from all nav items (including dropdown items)
253
+ const allNavItems = this.sidebar.querySelectorAll('.nav-item');
254
+ allNavItems.forEach(item => {
255
+ item.classList.remove('actived');
256
+ });
257
+
258
+ // Close all dropdowns first
259
+ this.closeAllDropdowns();
260
+
261
+ // Get current page filename
262
+ const currentPath = window.location.pathname;
263
+ const currentPage = currentPath.split('/').pop() || 'index.html';
264
+
265
+ // Find and activate the correct nav item
266
+ const allLinks = this.sidebar.querySelectorAll('a[href]');
267
+
268
+ allLinks.forEach(link => {
269
+ const href = link.getAttribute('href');
270
+ if (!href || href === 'javascript:void(0);' || href === 'javascript:void(0)') return;
271
+
272
+ // Extract filename from href
273
+ const linkPage = href.split('/').pop();
274
+
275
+ if (linkPage === currentPage) {
276
+ const navItem = link.closest('.nav-item') as HTMLElement;
277
+ if (navItem) {
278
+ navItem.classList.add('actived');
279
+ this.state.activeMenu = linkPage || null;
280
+
281
+ // If this is inside a dropdown, handle parent dropdown specially
282
+ const parentDropdown = navItem.closest('.dropdown-menu') as HTMLElement;
283
+ if (parentDropdown) {
284
+ const parentDropdownItem = parentDropdown.closest('.nav-item.dropdown') as HTMLElement;
285
+ if (parentDropdownItem) {
286
+ // Open the parent dropdown
287
+ parentDropdownItem.classList.add('open');
288
+ parentDropdown.style.display = 'block';
289
+
290
+ // Add special styling to indicate parent has active child
291
+ parentDropdownItem.classList.add('has-active-child');
292
+ }
293
+ }
294
+ }
295
+ }
296
+ });
297
+ }
298
+
299
+ /**
300
+ * Handle window resize
301
+ */
302
+ private handleResize(): void {
303
+ this.state.isMobile = window.innerWidth <= (this.options.breakpoint || 768);
304
+
305
+ if (this.options.autoHide && this.state.isMobile) {
306
+ // Auto-hide logic for mobile
307
+ this.collapse();
308
+ }
309
+ }
310
+
311
+ /**
312
+ * Setup event listeners
313
+ */
314
+ private setupEventListeners(): void {
315
+ window.addEventListener('resize', this.handleResize.bind(this));
316
+ }
317
+
318
+ /**
319
+ * Remove event listeners
320
+ */
321
+ private removeEventListeners(): void {
322
+ window.removeEventListener('resize', this.handleResize.bind(this));
323
+ }
324
+
325
+ /**
326
+ * Public method to refresh active links (useful for SPA navigation)
327
+ */
328
+ public refreshActiveLink(): void {
329
+ this.setActiveLink();
330
+ }
331
+
332
+ /**
333
+ * Public method to toggle sidebar programmatically
334
+ */
335
+ public toggle(): void {
336
+ this.toggleSidebar();
337
+ }
338
+
339
+ /**
340
+ * Public method to collapse sidebar
341
+ */
342
+ public collapse(): void {
343
+ if (!this.app || this.state.isCollapsed) return;
344
+
345
+ this.state.isCollapsed = true;
346
+ this.app.classList.add('is-collapsed');
347
+ }
348
+
349
+ /**
350
+ * Public method to expand sidebar
351
+ */
352
+ public expand(): void {
353
+ if (!this.app || !this.state.isCollapsed) return;
354
+
355
+ this.state.isCollapsed = false;
356
+ this.app.classList.remove('is-collapsed');
357
+ }
358
+
359
+ /**
360
+ * Public method to check if sidebar is collapsed
361
+ */
362
+ public isCollapsed(): boolean {
363
+ return this.state.isCollapsed;
364
+ }
365
+
366
+ /**
367
+ * Get current sidebar state
368
+ */
369
+ public getState(): SidebarState {
370
+ return { ...this.state };
371
+ }
372
+
373
+ /**
374
+ * Update sidebar options
375
+ */
376
+ public updateOptions(newOptions: Partial<SidebarOptions>): void {
377
+ this.options = { ...this.options, ...newOptions };
378
+ }
379
+
380
+ /**
381
+ * Get current options
382
+ */
383
+ public getOptions(): SidebarOptions {
384
+ return { ...this.options };
385
+ }
386
+ }
387
+
388
+ export default Sidebar;