diff --git a/HeadySystems_v13/apps/heady_admin_ui/ADVANCED_FEATURES.md b/HeadySystems_v13/apps/heady_admin_ui/ADVANCED_FEATURES.md new file mode 100644 index 00000000..7f9d43fb --- /dev/null +++ b/HeadySystems_v13/apps/heady_admin_ui/ADVANCED_FEATURES.md @@ -0,0 +1,309 @@ +# Advanced UI Features - Heady Admin Dashboard + +## Overview +This document describes the advanced UI features added to the Heady Admin Dashboard, including the command palette, keyboard shortcuts modal, toast notifications, and loading skeletons. + +## Features + +### 1. Command Palette +A Spotlight/VS Code style command palette for quick navigation and actions. + +**Trigger:** `Ctrl+K` (Windows/Linux) or `Cmd+K` (Mac) + +**Features:** +- Fuzzy search through all available commands +- Categorized commands (Navigation, Verticals, Actions, Help) +- Keyboard navigation with arrow keys +- Recent commands tracking +- Glassmorphism modal design + +**Available Commands:** +- Navigate to Dashboard, Verticals, Governance, Activity +- Navigate to each vertical (Make, Field, Legacy, Kinetic, Bio, Ed, Guard) +- Toggle theme (Dark/Light) +- Open notifications +- View keyboard shortcuts +- Search documentation + +**API:** +```javascript +// Open command palette programmatically +window.commandPalette.open(); + +// Close command palette +window.commandPalette.close(); + +// Toggle command palette +window.commandPalette.toggle(); +``` + +### 2. Keyboard Shortcuts Modal +A comprehensive reference for all keyboard shortcuts in the application. + +**Trigger:** `?` key (when not in an input field) + +**Categories:** +- **Navigation:** Page navigation shortcuts +- **Actions:** Theme toggle, notifications +- **Modals:** Modal interaction shortcuts + +**Shortcuts List:** +- `Ctrl/Cmd + K` - Open command palette +- `?` - Show keyboard shortcuts +- `Alt + D` - Go to Dashboard +- `Alt + V` - Go to Verticals +- `Alt + G` - Go to Governance +- `Alt + A` - Go to Activity +- `Alt + T` - Toggle theme +- `Alt + N` - Open notifications +- `Escape` - Close any modal + +**API:** +```javascript +// Open shortcuts modal programmatically +window.shortcutsModal.open(); + +// Close shortcuts modal +window.shortcutsModal.close(); + +// Toggle shortcuts modal +window.shortcutsModal.toggle(); +``` + +### 3. Toast Notification System +A modern toast notification system for user feedback. + +**Types:** +- `success` - Green with checkmark icon +- `error` - Red with X icon +- `warning` - Orange with warning icon +- `info` - Blue with info icon + +**Features:** +- Auto-dismiss with configurable duration (default 5 seconds) +- Manual dismiss with close button +- Stack multiple notifications +- Slide-in animation from top-right +- Progress bar showing time until auto-dismiss +- Accessible with ARIA live regions + +**API:** +```javascript +// Show a success toast +window.toastSystem.showToast({ + type: 'success', + title: 'Success!', + message: 'Operation completed successfully', + duration: 5000 // optional, default 5000ms, 0 = no auto-dismiss +}); + +// Show an error toast +window.toastSystem.showToast({ + type: 'error', + title: 'Error', + message: 'Something went wrong', + duration: 0 // Will not auto-dismiss +}); + +// Show a warning toast +window.toastSystem.showToast({ + type: 'warning', + title: 'Warning', + message: 'Please review your input' +}); + +// Show an info toast +window.toastSystem.showToast({ + type: 'info', + title: 'Info', + message: 'New update available' +}); + +// Dismiss a specific toast +window.toastSystem.dismissToast(toastId); + +// Dismiss all toasts +window.toastSystem.dismissAll(); +``` + +### 4. Loading Skeleton Screens +Skeleton loading states for improved perceived performance. + +**Components:** +- `.skeleton` - Base skeleton element with shimmer animation +- `.skeleton-text` - Text line skeleton (with width variants: `.w-full`, `.w-75`, `.w-50`, `.w-25`) +- `.skeleton-card` - Card skeleton with header, body, and footer +- `.skeleton-chart` - Chart skeleton with animated bars +- `.skeleton-avatar` - Circular avatar skeleton (sizes: `.small`, `.large`) +- `.skeleton-button` - Button skeleton +- `.skeleton-badge` - Badge skeleton + +**Usage:** +```html + +
+
+
+ + +
+
+
+
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+``` + +See `skeleton-demo.html` for more examples. + +## File Structure + +``` +HeadySystems_v13/apps/heady_admin_ui/ +├── css/ +│ ├── modals.css # Modal and toast styles +│ └── skeletons.css # Loading skeleton styles +├── js/ +│ ├── command-palette.js # Command palette implementation +│ ├── shortcuts.js # Keyboard shortcuts modal +│ └── toast.js # Toast notification system +├── index.html # Main dashboard (updated) +├── skeleton-demo.html # Skeleton components demo +└── ADVANCED_FEATURES.md # This file +``` + +## Design Philosophy + +### Glassmorphism +All modals use a glassmorphism design with: +- Frosted glass blur effect +- Semi-transparent backgrounds +- Subtle borders and shadows +- Smooth animations + +### Accessibility +- ARIA labels on all interactive elements +- Keyboard navigation support +- Focus management in modals +- Screen reader announcements +- High contrast mode support +- Reduced motion support for users who prefer it + +### Responsive Design +- Mobile-first approach +- Touch-friendly on mobile devices +- Adaptive layouts for different screen sizes +- Graceful degradation on older browsers + +### Performance +- CSS animations preferred over JavaScript +- Minimal DOM manipulation +- No external dependencies +- Lazy initialization + +## Browser Support +- Chrome 90+ +- Firefox 88+ +- Safari 14+ +- Edge 90+ + +## Integration Examples + +### Show toast on successful form submission +```javascript +document.querySelector('form').addEventListener('submit', async (e) => { + e.preventDefault(); + + try { + await submitForm(); + window.toastSystem.showToast({ + type: 'success', + title: 'Form Submitted', + message: 'Your data has been saved successfully' + }); + } catch (error) { + window.toastSystem.showToast({ + type: 'error', + title: 'Submission Failed', + message: error.message + }); + } +}); +``` + +### Show skeleton while loading data +```html + +
+
...
+
...
+
+ + +``` + +### Custom command in command palette +```javascript +// Add a custom command to the palette +window.commandPalette.commands.push({ + id: 'custom-action', + name: 'My Custom Action', + icon: '⚡', + category: 'Custom', + action: () => { + console.log('Custom action executed'); + window.toastSystem.showToast({ + type: 'success', + title: 'Custom Action', + message: 'Action completed' + }); + }, + keywords: ['custom', 'action', 'my'] +}); +``` + +## Theme Support +All features support both dark and light themes: +- Dark theme (default) +- Light theme (toggle with `Alt+T` or theme button) + +Themes are persisted in `localStorage` and apply automatically on page load. + +## Future Enhancements +Potential future improvements: +- Command palette command history +- Toast notification queue management +- More skeleton component variants +- Customizable keyboard shortcuts +- Command palette plugins/extensions +- Toast notification templates + +## License +Part of HeadySystems Inc. © 2026 | Invented by Eric Haywood diff --git a/HeadySystems_v13/apps/heady_admin_ui/css/modals.css b/HeadySystems_v13/apps/heady_admin_ui/css/modals.css new file mode 100644 index 00000000..25fd6e70 --- /dev/null +++ b/HeadySystems_v13/apps/heady_admin_ui/css/modals.css @@ -0,0 +1,705 @@ +/* ======================================== + Heady Admin UI - Modal Styles + Glassmorphism modals with blur effects + ======================================== */ + +/* Modal Overlay */ +.modal-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.7); + backdrop-filter: blur(10px); + -webkit-backdrop-filter: blur(10px); + display: flex; + align-items: center; + justify-content: center; + z-index: 9999; + padding: 1rem; + animation: modal-fade-in 0.3s ease; +} + +@keyframes modal-fade-in { + from { + opacity: 0; + } + to { + opacity: 1; + } +} + +/* Modal Content */ +.modal-content { + background: rgba(255, 255, 255, 0.1); + backdrop-filter: blur(20px); + -webkit-backdrop-filter: blur(20px); + border: 1px solid rgba(255, 255, 255, 0.2); + border-radius: var(--radius-xl); + box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5), + 0 0 0 1px rgba(255, 255, 255, 0.1) inset; + max-width: 600px; + width: 100%; + max-height: 80vh; + overflow: hidden; + animation: modal-slide-up 0.3s ease; +} + +@keyframes modal-slide-up { + from { + opacity: 0; + transform: translateY(30px) scale(0.95); + } + to { + opacity: 1; + transform: translateY(0) scale(1); + } +} + +/* Light theme adjustments */ +body.light-theme .modal-content { + background: rgba(255, 255, 255, 0.9); + border: 1px solid rgba(0, 0, 0, 0.1); +} + +/* ======================================== + Command Palette Styles + ======================================== */ + +.command-palette { + max-width: 640px; +} + +.command-palette-header { + padding: 1.5rem; + border-bottom: 1px solid rgba(255, 255, 255, 0.1); +} + +.command-palette-input { + width: 100%; + background: rgba(255, 255, 255, 0.1); + border: 1px solid rgba(255, 255, 255, 0.2); + border-radius: var(--radius-md); + padding: 0.75rem 1rem; + font-size: 1rem; + color: var(--text-primary); + outline: none; + transition: all 0.2s ease; +} + +.command-palette-input::placeholder { + color: var(--text-secondary); +} + +.command-palette-input:focus { + background: rgba(255, 255, 255, 0.15); + border-color: var(--color-primary); + box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.2); +} + +body.light-theme .command-palette-input { + background: rgba(0, 0, 0, 0.05); + border: 1px solid rgba(0, 0, 0, 0.1); +} + +body.light-theme .command-palette-input:focus { + background: rgba(0, 0, 0, 0.08); +} + +.command-palette-body { + max-height: 400px; + overflow-y: auto; + overflow-x: hidden; +} + +/* Custom scrollbar */ +.command-palette-body::-webkit-scrollbar { + width: 8px; +} + +.command-palette-body::-webkit-scrollbar-track { + background: rgba(255, 255, 255, 0.05); +} + +.command-palette-body::-webkit-scrollbar-thumb { + background: rgba(255, 255, 255, 0.2); + border-radius: 4px; +} + +.command-palette-body::-webkit-scrollbar-thumb:hover { + background: rgba(255, 255, 255, 0.3); +} + +.command-results { + padding: 0.5rem; +} + +.command-section { + margin-bottom: 1rem; +} + +.command-section:last-child { + margin-bottom: 0; +} + +.command-section-title { + font-size: 0.75rem; + font-weight: 600; + text-transform: uppercase; + color: var(--text-secondary); + padding: 0.5rem 0.75rem; + letter-spacing: 0.05em; +} + +.command-item { + display: flex; + align-items: center; + gap: 0.75rem; + padding: 0.75rem 1rem; + border-radius: var(--radius-md); + cursor: pointer; + transition: all 0.15s ease; + margin: 0.25rem 0; +} + +.command-item:hover, +.command-item.selected { + background: rgba(99, 102, 241, 0.2); + transform: translateX(4px); +} + +body.light-theme .command-item:hover, +body.light-theme .command-item.selected { + background: rgba(99, 102, 241, 0.15); +} + +.command-icon { + font-size: 1.25rem; + display: flex; + align-items: center; + justify-content: center; + width: 2rem; + height: 2rem; + flex-shrink: 0; +} + +.command-name { + flex: 1; + font-size: 0.9rem; + color: var(--text-primary); +} + +.no-results { + padding: 2rem; + text-align: center; + color: var(--text-secondary); + font-size: 0.9rem; +} + +.command-palette-footer { + padding: 1rem 1.5rem; + border-top: 1px solid rgba(255, 255, 255, 0.1); +} + +.command-palette-hints { + display: flex; + gap: 1.5rem; + justify-content: center; + flex-wrap: wrap; + font-size: 0.75rem; + color: var(--text-secondary); +} + +.command-palette-hints kbd { + background: rgba(255, 255, 255, 0.1); + border: 1px solid rgba(255, 255, 255, 0.2); + border-radius: 4px; + padding: 0.25rem 0.5rem; + font-family: var(--font-mono); + font-size: 0.7rem; + margin: 0 0.25rem; +} + +body.light-theme .command-palette-hints kbd { + background: rgba(0, 0, 0, 0.08); + border: 1px solid rgba(0, 0, 0, 0.15); +} + +/* ======================================== + Shortcuts Modal Styles + ======================================== */ + +.shortcuts-modal { + max-width: 700px; +} + +.shortcuts-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 1.5rem; + border-bottom: 1px solid rgba(255, 255, 255, 0.1); +} + +.shortcuts-header h2 { + display: flex; + align-items: center; + gap: 0.5rem; + font-size: 1.5rem; + font-weight: 600; + color: var(--text-primary); + margin: 0; +} + +.modal-close { + background: rgba(255, 255, 255, 0.1); + border: 1px solid rgba(255, 255, 255, 0.2); + border-radius: var(--radius-md); + width: 2rem; + height: 2rem; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + transition: all 0.2s ease; + color: var(--text-primary); + font-size: 1.25rem; +} + +.modal-close:hover { + background: rgba(255, 255, 255, 0.2); + transform: scale(1.1); +} + +body.light-theme .modal-close { + background: rgba(0, 0, 0, 0.05); + border: 1px solid rgba(0, 0, 0, 0.1); +} + +.shortcuts-body { + padding: 1.5rem; + max-height: 60vh; + overflow-y: auto; +} + +.shortcuts-body::-webkit-scrollbar { + width: 8px; +} + +.shortcuts-body::-webkit-scrollbar-track { + background: rgba(255, 255, 255, 0.05); +} + +.shortcuts-body::-webkit-scrollbar-thumb { + background: rgba(255, 255, 255, 0.2); + border-radius: 4px; +} + +.shortcuts-section { + margin-bottom: 2rem; +} + +.shortcuts-section:last-child { + margin-bottom: 0; +} + +.shortcuts-category { + font-size: 1rem; + font-weight: 600; + color: var(--color-primary); + margin-bottom: 1rem; + display: flex; + align-items: center; + gap: 0.5rem; +} + +.shortcuts-list { + display: flex; + flex-direction: column; + gap: 0.75rem; +} + +.shortcut-item { + display: flex; + align-items: center; + justify-content: space-between; + gap: 1rem; + padding: 0.75rem 1rem; + background: rgba(255, 255, 255, 0.05); + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: var(--radius-md); + transition: all 0.2s ease; +} + +.shortcut-item:hover { + background: rgba(255, 255, 255, 0.08); + transform: translateX(4px); +} + +body.light-theme .shortcut-item { + background: rgba(0, 0, 0, 0.03); + border: 1px solid rgba(0, 0, 0, 0.08); +} + +body.light-theme .shortcut-item:hover { + background: rgba(0, 0, 0, 0.05); +} + +.shortcut-keys { + display: flex; + gap: 0.5rem; + align-items: center; + flex-shrink: 0; +} + +.shortcut-keys kbd { + background: rgba(255, 255, 255, 0.15); + border: 1px solid rgba(255, 255, 255, 0.25); + border-bottom-width: 3px; + border-radius: 6px; + padding: 0.4rem 0.75rem; + font-family: var(--font-mono); + font-size: 0.8rem; + font-weight: 600; + color: var(--text-primary); + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); + min-width: 2rem; + text-align: center; +} + +body.light-theme .shortcut-keys kbd { + background: rgba(0, 0, 0, 0.08); + border: 1px solid rgba(0, 0, 0, 0.15); + border-bottom-width: 3px; +} + +.shortcut-description { + flex: 1; + font-size: 0.9rem; + color: var(--text-secondary); +} + +.shortcuts-footer { + padding: 1rem 1.5rem; + border-top: 1px solid rgba(255, 255, 255, 0.1); + background: rgba(0, 0, 0, 0.2); +} + +.shortcuts-hint { + display: flex; + align-items: center; + justify-content: center; + gap: 0.5rem; + font-size: 0.85rem; + color: var(--text-secondary); + margin: 0; +} + +.shortcuts-hint kbd { + background: rgba(255, 255, 255, 0.1); + border: 1px solid rgba(255, 255, 255, 0.2); + border-radius: 4px; + padding: 0.25rem 0.5rem; + font-family: var(--font-mono); + font-size: 0.75rem; +} + +/* ======================================== + Responsive Design + ======================================== */ + +@media (max-width: 768px) { + .modal-content { + max-width: 100%; + max-height: 90vh; + margin: 0; + border-radius: var(--radius-lg); + } + + .command-palette, + .shortcuts-modal { + max-width: 100%; + } + + .command-palette-hints, + .shortcuts-hint { + font-size: 0.7rem; + } + + .shortcut-item { + flex-direction: column; + align-items: flex-start; + gap: 0.5rem; + } + + .shortcut-keys { + order: 2; + } + + .shortcut-description { + order: 1; + } +} + +@media (max-width: 480px) { + .modal-overlay { + padding: 0; + } + + .modal-content { + border-radius: 0; + max-height: 100vh; + } + + .command-palette-header, + .shortcuts-header { + padding: 1rem; + } + + .shortcuts-body { + padding: 1rem; + } +} + +/* ======================================== + Toast Notification Styles + ======================================== */ + +.toast-container { + position: fixed; + top: 1rem; + right: 1rem; + z-index: 10000; + display: flex; + flex-direction: column; + gap: 0.75rem; + pointer-events: none; + max-width: 400px; +} + +.toast { + background: rgba(255, 255, 255, 0.1); + backdrop-filter: blur(20px); + -webkit-backdrop-filter: blur(20px); + border: 1px solid rgba(255, 255, 255, 0.2); + border-radius: var(--radius-lg); + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.4), + 0 0 0 1px rgba(255, 255, 255, 0.1) inset; + padding: 1rem; + display: flex; + align-items: flex-start; + gap: 0.75rem; + min-width: 320px; + pointer-events: all; + position: relative; + overflow: hidden; +} + +body.light-theme .toast { + background: rgba(255, 255, 255, 0.95); + border: 1px solid rgba(0, 0, 0, 0.1); +} + +/* Toast entrance animation */ +.toast-enter { + opacity: 0; + transform: translateX(100%) scale(0.9); + animation: toast-slide-in 0.3s ease forwards; +} + +@keyframes toast-slide-in { + to { + opacity: 1; + transform: translateX(0) scale(1); + } +} + +/* Toast exit animation */ +.toast-exit { + animation: toast-slide-out 0.3s ease forwards; +} + +@keyframes toast-slide-out { + to { + opacity: 0; + transform: translateX(100%) scale(0.9); + } +} + +/* Toast icon */ +.toast-icon { + width: 2rem; + height: 2rem; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-size: 1rem; + font-weight: bold; + flex-shrink: 0; +} + +.toast-icon-success { + background: var(--color-success); + color: white; +} + +.toast-icon-error { + background: var(--color-error); + color: white; +} + +.toast-icon-warning { + background: var(--color-warning); + color: white; +} + +.toast-icon-info { + background: var(--color-info); + color: white; +} + +/* Toast content */ +.toast-content { + flex: 1; + min-width: 0; +} + +.toast-title { + font-weight: 600; + font-size: 0.9rem; + color: var(--text-primary); + margin-bottom: 0.25rem; +} + +.toast-message { + font-size: 0.85rem; + color: var(--text-secondary); + line-height: 1.4; +} + +/* Toast close button */ +.toast-close { + background: transparent; + border: none; + color: var(--text-secondary); + font-size: 1.25rem; + cursor: pointer; + padding: 0; + width: 1.5rem; + height: 1.5rem; + display: flex; + align-items: center; + justify-content: center; + border-radius: 4px; + transition: all 0.2s ease; + flex-shrink: 0; +} + +.toast-close:hover { + background: rgba(255, 255, 255, 0.1); + color: var(--text-primary); +} + +body.light-theme .toast-close:hover { + background: rgba(0, 0, 0, 0.05); +} + +/* Toast progress bar */ +.toast-progress { + position: absolute; + bottom: 0; + left: 0; + right: 0; + height: 3px; + background: rgba(255, 255, 255, 0.1); + overflow: hidden; +} + +.toast-progress-bar { + height: 100%; + width: 100%; + background: currentColor; +} + +.toast-success .toast-progress-bar { + color: var(--color-success); +} + +.toast-error .toast-progress-bar { + color: var(--color-error); +} + +.toast-warning .toast-progress-bar { + color: var(--color-warning); +} + +.toast-info .toast-progress-bar { + color: var(--color-info); +} + +/* Toast type variants with left border accent */ +.toast-success { + border-left: 4px solid var(--color-success); +} + +.toast-error { + border-left: 4px solid var(--color-error); +} + +.toast-warning { + border-left: 4px solid var(--color-warning); +} + +.toast-info { + border-left: 4px solid var(--color-info); +} + +/* ======================================== + Accessibility + ======================================== */ + +/* Reduce motion for users who prefer it */ +@media (prefers-reduced-motion: reduce) { + .modal-overlay, + .modal-content, + .command-item, + .shortcut-item, + .toast { + animation: none; + transition: none; + } +} + +/* High contrast mode */ +@media (prefers-contrast: high) { + .modal-content { + border: 2px solid var(--text-primary); + } + + .command-item.selected, + .shortcut-item:hover { + outline: 2px solid var(--color-primary); + } + + .toast { + border: 2px solid currentColor; + } +} + +/* Mobile responsiveness for toasts */ +@media (max-width: 768px) { + .toast-container { + top: 0; + right: 0; + left: 0; + max-width: 100%; + padding: 1rem; + } + + .toast { + min-width: 0; + width: 100%; + } +} diff --git a/HeadySystems_v13/apps/heady_admin_ui/css/skeletons.css b/HeadySystems_v13/apps/heady_admin_ui/css/skeletons.css new file mode 100644 index 00000000..8d20f150 --- /dev/null +++ b/HeadySystems_v13/apps/heady_admin_ui/css/skeletons.css @@ -0,0 +1,395 @@ +/* ======================================== + Heady Admin UI - Skeleton Loading States + Shimmer effect loading placeholders + ======================================== */ + +/* Base Skeleton Styles */ +.skeleton { + background: linear-gradient( + 90deg, + rgba(255, 255, 255, 0.05) 0%, + rgba(255, 255, 255, 0.1) 40%, + rgba(255, 255, 255, 0.05) 80%, + rgba(255, 255, 255, 0.05) 100% + ); + background-size: 200% 100%; + animation: skeleton-shimmer 2s ease-in-out infinite; + border-radius: var(--radius-md); +} + +/* Light theme skeleton */ +body.light-theme .skeleton { + background: linear-gradient( + 90deg, + rgba(0, 0, 0, 0.05) 0%, + rgba(0, 0, 0, 0.1) 40%, + rgba(0, 0, 0, 0.05) 80%, + rgba(0, 0, 0, 0.05) 100% + ); + background-size: 200% 100%; +} + +@keyframes skeleton-shimmer { + 0% { + background-position: 200% 0; + } + 100% { + background-position: -200% 0; + } +} + +/* Skeleton Text Lines */ +.skeleton-text { + height: 1rem; + margin-bottom: 0.75rem; + border-radius: 4px; +} + +.skeleton-text:last-child { + margin-bottom: 0; +} + +/* Different text widths for natural look */ +.skeleton-text.w-full { + width: 100%; +} + +.skeleton-text.w-75 { + width: 75%; +} + +.skeleton-text.w-50 { + width: 50%; +} + +.skeleton-text.w-25 { + width: 25%; +} + +/* Skeleton heading - larger */ +.skeleton-text.heading { + height: 1.75rem; + width: 60%; + margin-bottom: 1.5rem; +} + +/* Skeleton subheading */ +.skeleton-text.subheading { + height: 1.25rem; + width: 40%; + margin-bottom: 1rem; +} + +/* Skeleton Card */ +.skeleton-card { + background: rgba(255, 255, 255, 0.05); + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: var(--radius-lg); + padding: 1.5rem; + position: relative; + overflow: hidden; +} + +body.light-theme .skeleton-card { + background: rgba(0, 0, 0, 0.03); + border: 1px solid rgba(0, 0, 0, 0.08); +} + +/* Shimmer overlay for cards */ +.skeleton-card::before { + content: ''; + position: absolute; + top: 0; + left: -100%; + width: 100%; + height: 100%; + background: linear-gradient( + 90deg, + transparent 0%, + rgba(255, 255, 255, 0.1) 50%, + transparent 100% + ); + animation: skeleton-card-shimmer 2s ease-in-out infinite; +} + +@keyframes skeleton-card-shimmer { + 0% { + left: -100%; + } + 100% { + left: 100%; + } +} + +/* Skeleton card content */ +.skeleton-card-header { + display: flex; + align-items: center; + gap: 1rem; + margin-bottom: 1.5rem; +} + +.skeleton-card-icon { + width: 3rem; + height: 3rem; + border-radius: var(--radius-md); +} + +.skeleton-card-title { + flex: 1; +} + +.skeleton-card-body { + display: flex; + flex-direction: column; + gap: 0.75rem; +} + +.skeleton-card-footer { + margin-top: 1.5rem; + padding-top: 1rem; + border-top: 1px solid rgba(255, 255, 255, 0.1); +} + +body.light-theme .skeleton-card-footer { + border-top: 1px solid rgba(0, 0, 0, 0.08); +} + +/* Skeleton Chart */ +.skeleton-chart { + width: 100%; + height: 250px; + background: rgba(255, 255, 255, 0.05); + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: var(--radius-lg); + position: relative; + overflow: hidden; +} + +body.light-theme .skeleton-chart { + background: rgba(0, 0, 0, 0.03); + border: 1px solid rgba(0, 0, 0, 0.08); +} + +/* Chart bars animation */ +.skeleton-chart-bars { + display: flex; + align-items: flex-end; + justify-content: space-around; + height: 100%; + padding: 2rem 1rem; + gap: 0.5rem; +} + +.skeleton-chart-bar { + flex: 1; + background: rgba(255, 255, 255, 0.1); + border-radius: 4px 4px 0 0; + animation: skeleton-chart-pulse 1.5s ease-in-out infinite; +} + +body.light-theme .skeleton-chart-bar { + background: rgba(0, 0, 0, 0.08); +} + +@keyframes skeleton-chart-pulse { + 0%, 100% { + opacity: 0.5; + } + 50% { + opacity: 1; + } +} + +/* Different heights for natural chart look */ +.skeleton-chart-bar:nth-child(1) { + height: 60%; + animation-delay: 0s; +} + +.skeleton-chart-bar:nth-child(2) { + height: 85%; + animation-delay: 0.2s; +} + +.skeleton-chart-bar:nth-child(3) { + height: 45%; + animation-delay: 0.4s; +} + +.skeleton-chart-bar:nth-child(4) { + height: 70%; + animation-delay: 0.6s; +} + +.skeleton-chart-bar:nth-child(5) { + height: 95%; + animation-delay: 0.8s; +} + +.skeleton-chart-bar:nth-child(6) { + height: 55%; + animation-delay: 1s; +} + +/* Skeleton Avatar/Circle */ +.skeleton-avatar { + width: 3rem; + height: 3rem; + border-radius: 50%; +} + +.skeleton-avatar.small { + width: 2rem; + height: 2rem; +} + +.skeleton-avatar.large { + width: 5rem; + height: 5rem; +} + +/* Skeleton Button */ +.skeleton-button { + height: 2.5rem; + width: 120px; + border-radius: var(--radius-md); +} + +.skeleton-button.full { + width: 100%; +} + +/* Skeleton Grid Layout */ +.skeleton-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); + gap: 1.5rem; +} + +/* Skeleton List */ +.skeleton-list { + display: flex; + flex-direction: column; + gap: 1rem; +} + +.skeleton-list-item { + display: flex; + align-items: center; + gap: 1rem; + padding: 1rem; + background: rgba(255, 255, 255, 0.05); + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: var(--radius-md); +} + +body.light-theme .skeleton-list-item { + background: rgba(0, 0, 0, 0.03); + border: 1px solid rgba(0, 0, 0, 0.08); +} + +.skeleton-list-item-content { + flex: 1; +} + +/* Skeleton Badge */ +.skeleton-badge { + height: 1.5rem; + width: 60px; + border-radius: var(--radius-full); +} + +/* Skeleton Table */ +.skeleton-table { + width: 100%; + border-collapse: separate; + border-spacing: 0 0.5rem; +} + +.skeleton-table-row { + background: rgba(255, 255, 255, 0.05); + border-radius: var(--radius-md); +} + +body.light-theme .skeleton-table-row { + background: rgba(0, 0, 0, 0.03); +} + +.skeleton-table-cell { + padding: 1rem; +} + +.skeleton-table-cell:first-child { + border-radius: var(--radius-md) 0 0 var(--radius-md); +} + +.skeleton-table-cell:last-child { + border-radius: 0 var(--radius-md) var(--radius-md) 0; +} + +/* Skeleton State Container */ +.skeleton-container { + padding: 2rem; +} + +.skeleton-container.centered { + display: flex; + align-items: center; + justify-content: center; + min-height: 400px; +} + +/* Responsive */ +@media (max-width: 768px) { + .skeleton-grid { + grid-template-columns: 1fr; + } + + .skeleton-chart { + height: 200px; + } + + .skeleton-card { + padding: 1rem; + } +} + +/* Accessibility - Reduce motion */ +@media (prefers-reduced-motion: reduce) { + .skeleton, + .skeleton-card::before, + .skeleton-chart-bar { + animation: none; + } +} + +/* Dark mode specific adjustments */ +@media (prefers-color-scheme: dark) { + .skeleton { + background: linear-gradient( + 90deg, + rgba(255, 255, 255, 0.03) 0%, + rgba(255, 255, 255, 0.08) 40%, + rgba(255, 255, 255, 0.03) 80%, + rgba(255, 255, 255, 0.03) 100% + ); + } +} + +/* Example usage classes for convenience */ +.loading { + pointer-events: none; + user-select: none; +} + +.loading * { + visibility: hidden; +} + +.loading .skeleton, +.loading .skeleton-card, +.loading .skeleton-chart, +.loading .skeleton-text { + visibility: visible; +} diff --git a/HeadySystems_v13/apps/heady_admin_ui/index.html b/HeadySystems_v13/apps/heady_admin_ui/index.html index 0087274d..a61b4a4b 100644 --- a/HeadySystems_v13/apps/heady_admin_ui/index.html +++ b/HeadySystems_v13/apps/heady_admin_ui/index.html @@ -7,6 +7,8 @@ Heady Admin Dashboard + + @@ -467,6 +469,9 @@

+ + + diff --git a/HeadySystems_v13/apps/heady_admin_ui/js/command-palette.js b/HeadySystems_v13/apps/heady_admin_ui/js/command-palette.js new file mode 100644 index 00000000..b4a5c5c9 --- /dev/null +++ b/HeadySystems_v13/apps/heady_admin_ui/js/command-palette.js @@ -0,0 +1,497 @@ +/** + * Heady Admin UI - Command Palette + * Spotlight/VS Code style command palette with fuzzy search + */ + +class CommandPalette { + constructor() { + this.isOpen = false; + this.commands = []; + this.filteredCommands = []; + this.selectedIndex = 0; + this.recentCommands = this.loadRecentCommands(); + this.maxRecentCommands = 5; + + this.initCommands(); + this.createModal(); + this.bindEvents(); + } + + initCommands() { + this.commands = [ + // Navigation Commands + { + id: 'nav-dashboard', + name: 'Navigate to Dashboard', + icon: '📊', + category: 'Navigation', + action: () => this.navigateTo('dashboard'), + keywords: ['dashboard', 'home', 'main'] + }, + { + id: 'nav-verticals', + name: 'Navigate to Verticals', + icon: '🎯', + category: 'Navigation', + action: () => this.navigateTo('verticals'), + keywords: ['verticals', 'apps', 'applications'] + }, + { + id: 'nav-governance', + name: 'Navigate to Governance', + icon: '🔒', + category: 'Navigation', + action: () => this.navigateTo('governance'), + keywords: ['governance', 'compliance', 'security', 'audit'] + }, + { + id: 'nav-activity', + name: 'Navigate to Activity', + icon: '📈', + category: 'Navigation', + action: () => this.navigateTo('activity'), + keywords: ['activity', 'metrics', 'logs'] + }, + // Vertical-specific Navigation + { + id: 'nav-make', + name: 'Navigate to HeadyMake', + icon: '🏭', + category: 'Verticals', + action: () => window.location.href = '../heady_make/dashboard.html', + keywords: ['make', '3d', 'printing', 'manufacturing'] + }, + { + id: 'nav-field', + name: 'Navigate to HeadyField', + icon: '🌾', + category: 'Verticals', + action: () => window.location.href = '../heady_field/dashboard.html', + keywords: ['field', 'agriculture', 'iot', 'sensors'] + }, + { + id: 'nav-legacy', + name: 'Navigate to HeadyLegacy', + icon: '👤', + category: 'Verticals', + action: () => window.location.href = '../heady_legacy/dashboard.html', + keywords: ['legacy', 'biometric', 'identity', 'auth'] + }, + { + id: 'nav-kinetic', + name: 'Navigate to HeadyKinetic', + icon: '🔥', + category: 'Verticals', + action: () => window.location.href = '../heady_kinetic/dashboard.html', + keywords: ['kinetic', 'thermal', 'energy', 'temperature'] + }, + { + id: 'nav-bio', + name: 'Navigate to HeadyBio', + icon: '🧬', + category: 'Verticals', + action: () => this.showComingSoon('HeadyBio'), + keywords: ['bio', 'biometric', 'research', 'analysis'] + }, + { + id: 'nav-ed', + name: 'Navigate to HeadyEd', + icon: '📚', + category: 'Verticals', + action: () => this.showComingSoon('HeadyEd'), + keywords: ['ed', 'education', 'learning', 'courses'] + }, + { + id: 'nav-guard', + name: 'Navigate to HeadyGuard', + icon: '🛡️', + category: 'Verticals', + action: () => this.showComingSoon('HeadyGuard'), + keywords: ['guard', 'security', 'protection', 'monitoring'] + }, + // Actions + { + id: 'toggle-theme', + name: 'Toggle Theme (Dark/Light)', + icon: '🌓', + category: 'Actions', + action: () => { + document.querySelector('.theme-toggle')?.click(); + this.close(); + }, + keywords: ['theme', 'dark', 'light', 'mode', 'appearance'] + }, + { + id: 'open-notifications', + name: 'Open Notifications', + icon: '🔔', + category: 'Actions', + action: () => { + document.querySelector('.notification-btn')?.click(); + this.close(); + }, + keywords: ['notifications', 'alerts', 'messages'] + }, + { + id: 'view-shortcuts', + name: 'View Keyboard Shortcuts', + icon: '⌨️', + category: 'Help', + action: () => { + this.close(); + setTimeout(() => { + if (window.shortcutsModal) { + window.shortcutsModal.open(); + } + }, 300); + }, + keywords: ['shortcuts', 'keyboard', 'hotkeys', 'help'] + }, + { + id: 'search-docs', + name: 'Search Documentation', + icon: '📖', + category: 'Help', + action: () => { + this.close(); + window.open('https://docs.headysystems.com', '_blank'); + }, + keywords: ['docs', 'documentation', 'help', 'guide', 'manual'] + } + ]; + + this.filteredCommands = [...this.commands]; + } + + createModal() { + const modal = document.createElement('div'); + modal.id = 'command-palette-modal'; + modal.className = 'modal-overlay'; + modal.setAttribute('role', 'dialog'); + modal.setAttribute('aria-modal', 'true'); + modal.setAttribute('aria-labelledby', 'command-palette-title'); + modal.style.display = 'none'; + + modal.innerHTML = ` + + `; + + document.body.appendChild(modal); + this.modal = modal; + this.input = modal.querySelector('#command-palette-input'); + this.results = modal.querySelector('#command-palette-results'); + } + + bindEvents() { + // Keyboard shortcut to open palette + document.addEventListener('keydown', (e) => { + // Ctrl+K or Cmd+K + if ((e.ctrlKey || e.metaKey) && e.key === 'k') { + e.preventDefault(); + this.toggle(); + } + + // Handle navigation when palette is open + if (this.isOpen) { + if (e.key === 'Escape') { + e.preventDefault(); + this.close(); + } else if (e.key === 'ArrowDown') { + e.preventDefault(); + this.selectNext(); + } else if (e.key === 'ArrowUp') { + e.preventDefault(); + this.selectPrevious(); + } else if (e.key === 'Enter') { + e.preventDefault(); + this.executeSelected(); + } + } + }); + + // Click outside to close + this.modal.addEventListener('click', (e) => { + if (e.target === this.modal) { + this.close(); + } + }); + + // Input search + this.input.addEventListener('input', () => { + this.search(this.input.value); + }); + } + + search(query) { + if (!query.trim()) { + this.filteredCommands = [...this.commands]; + this.renderResults(); + return; + } + + const searchQuery = query.toLowerCase(); + + // Fuzzy search implementation + this.filteredCommands = this.commands.filter(cmd => { + const searchText = `${cmd.name} ${cmd.keywords.join(' ')}`.toLowerCase(); + + // Simple fuzzy matching: check if all characters appear in order + let searchIndex = 0; + for (let char of searchQuery) { + searchIndex = searchText.indexOf(char, searchIndex); + if (searchIndex === -1) return false; + searchIndex++; + } + return true; + }).sort((a, b) => { + // Prioritize matches at the start + const aStart = a.name.toLowerCase().startsWith(searchQuery); + const bStart = b.name.toLowerCase().startsWith(searchQuery); + if (aStart && !bStart) return -1; + if (!aStart && bStart) return 1; + return 0; + }); + + this.selectedIndex = 0; + this.renderResults(); + } + + renderResults() { + const showRecent = !this.input.value.trim() && this.recentCommands.length > 0; + + if (showRecent) { + this.renderRecentCommands(); + } else if (this.filteredCommands.length === 0) { + this.results.innerHTML = '
No commands found
'; + } else { + this.renderCommandList(); + } + } + + renderRecentCommands() { + const recentCmds = this.recentCommands + .map(id => this.commands.find(cmd => cmd.id === id)) + .filter(cmd => cmd); + + let html = '
Recent
'; + + recentCmds.forEach((cmd, index) => { + html += this.renderCommandItem(cmd, index === this.selectedIndex); + }); + + html += '
'; + + if (this.commands.length > 0) { + html += '
All Commands
'; + this.commands.slice(0, 8).forEach((cmd, index) => { + if (!this.recentCommands.includes(cmd.id)) { + html += this.renderCommandItem(cmd, false); + } + }); + html += '
'; + } + + this.results.innerHTML = html; + this.bindResultEvents(); + } + + renderCommandList() { + // Group by category + const grouped = {}; + this.filteredCommands.forEach(cmd => { + if (!grouped[cmd.category]) { + grouped[cmd.category] = []; + } + grouped[cmd.category].push(cmd); + }); + + let html = ''; + Object.entries(grouped).forEach(([category, cmds]) => { + html += `
${category}
`; + cmds.forEach((cmd, index) => { + const globalIndex = this.filteredCommands.indexOf(cmd); + html += this.renderCommandItem(cmd, globalIndex === this.selectedIndex); + }); + html += '
'; + }); + + this.results.innerHTML = html; + this.bindResultEvents(); + } + + renderCommandItem(cmd, isSelected) { + return ` +
+ ${cmd.icon} + ${cmd.name} +
+ `; + } + + bindResultEvents() { + this.results.querySelectorAll('.command-item').forEach(item => { + item.addEventListener('click', () => { + const cmdId = item.dataset.commandId; + const cmd = this.commands.find(c => c.id === cmdId); + if (cmd) { + this.executeCommand(cmd); + } + }); + + item.addEventListener('mouseenter', () => { + const cmdId = item.dataset.commandId; + this.selectedIndex = this.filteredCommands.findIndex(c => c.id === cmdId); + this.updateSelection(); + }); + }); + } + + selectNext() { + this.selectedIndex = (this.selectedIndex + 1) % this.filteredCommands.length; + this.updateSelection(); + } + + selectPrevious() { + this.selectedIndex = this.selectedIndex - 1; + if (this.selectedIndex < 0) { + this.selectedIndex = this.filteredCommands.length - 1; + } + this.updateSelection(); + } + + updateSelection() { + this.results.querySelectorAll('.command-item').forEach((item, index) => { + if (index === this.selectedIndex) { + item.classList.add('selected'); + item.setAttribute('aria-selected', 'true'); + item.scrollIntoView({ block: 'nearest', behavior: 'smooth' }); + } else { + item.classList.remove('selected'); + item.setAttribute('aria-selected', 'false'); + } + }); + } + + executeSelected() { + if (this.filteredCommands.length > 0) { + const cmd = this.filteredCommands[this.selectedIndex]; + this.executeCommand(cmd); + } + } + + executeCommand(cmd) { + this.addToRecent(cmd.id); + cmd.action(); + this.close(); + } + + navigateTo(view) { + const navItem = document.querySelector(`[data-view="${view}"]`); + if (navItem) { + navItem.click(); + this.close(); + } + } + + showComingSoon(vertical) { + if (window.toastSystem) { + window.toastSystem.showToast({ + type: 'info', + title: 'Coming Soon', + message: `${vertical} is currently in development`, + duration: 3000 + }); + } + this.close(); + } + + addToRecent(cmdId) { + // Remove if already exists + this.recentCommands = this.recentCommands.filter(id => id !== cmdId); + // Add to front + this.recentCommands.unshift(cmdId); + // Keep only max recent + this.recentCommands = this.recentCommands.slice(0, this.maxRecentCommands); + // Save to localStorage + this.saveRecentCommands(); + } + + loadRecentCommands() { + try { + const saved = localStorage.getItem('heady-recent-commands'); + return saved ? JSON.parse(saved) : []; + } catch { + return []; + } + } + + saveRecentCommands() { + try { + localStorage.setItem('heady-recent-commands', JSON.stringify(this.recentCommands)); + } catch { + // Ignore errors + } + } + + open() { + this.isOpen = true; + this.modal.style.display = 'flex'; + this.input.value = ''; + this.filteredCommands = [...this.commands]; + this.selectedIndex = 0; + this.renderResults(); + + // Focus input after animation + setTimeout(() => { + this.input.focus(); + }, 100); + + // Prevent body scroll + document.body.style.overflow = 'hidden'; + } + + close() { + this.isOpen = false; + this.modal.style.display = 'none'; + document.body.style.overflow = ''; + } + + toggle() { + if (this.isOpen) { + this.close(); + } else { + this.open(); + } + } +} + +// Initialize command palette when DOM is ready +if (typeof window !== 'undefined') { + window.addEventListener('DOMContentLoaded', () => { + window.commandPalette = new CommandPalette(); + }); +} diff --git a/HeadySystems_v13/apps/heady_admin_ui/js/main.js b/HeadySystems_v13/apps/heady_admin_ui/js/main.js index b3e55276..1d730fc3 100644 --- a/HeadySystems_v13/apps/heady_admin_ui/js/main.js +++ b/HeadySystems_v13/apps/heady_admin_ui/js/main.js @@ -12,10 +12,21 @@ function initThemeToggle() { themeToggle.addEventListener('click', () => { body.classList.toggle('light-theme'); const icon = themeToggle.querySelector('.icon'); - icon.textContent = body.classList.contains('light-theme') ? '☀️' : '🌙'; + const isLight = body.classList.contains('light-theme'); + icon.textContent = isLight ? '☀️' : '🌙'; // Save preference - localStorage.setItem('theme', body.classList.contains('light-theme') ? 'light' : 'dark'); + localStorage.setItem('theme', isLight ? 'light' : 'dark'); + + // Show toast notification + if (window.toastSystem) { + window.toastSystem.showToast({ + type: 'info', + title: 'Theme Changed', + message: `Switched to ${isLight ? 'light' : 'dark'} mode`, + duration: 2000 + }); + } }); // Load saved theme @@ -44,6 +55,23 @@ function initNavigation() { const view = item.dataset.view; console.log(`Navigating to: ${view}`); + // Show toast for navigation + if (window.toastSystem) { + const viewNames = { + dashboard: 'Dashboard', + verticals: 'Verticals', + governance: 'Governance', + activity: 'Activity' + }; + + window.toastSystem.showToast({ + type: 'info', + title: 'Navigation', + message: `Switched to ${viewNames[view] || view}`, + duration: 2000 + }); + } + // In a real app, this would switch views // For now, just log the action }); @@ -267,11 +295,23 @@ function initKeyboardNavigation() { document.querySelector('[data-view="governance"]')?.click(); } + // Alt + A for Activity + if (e.altKey && e.key === 'a') { + e.preventDefault(); + document.querySelector('[data-view="activity"]')?.click(); + } + // Alt + T for Theme Toggle if (e.altKey && e.key === 't') { e.preventDefault(); document.querySelector('.theme-toggle')?.click(); } + + // Alt + N for Notifications + if (e.altKey && e.key === 'n') { + e.preventDefault(); + document.querySelector('.notification-btn')?.click(); + } }); } @@ -314,10 +354,24 @@ document.addEventListener('DOMContentLoaded', () => { playWelcomeAnimation(); }, 100); + // Show welcome toast after page loads + setTimeout(() => { + if (window.toastSystem) { + window.toastSystem.showToast({ + type: 'success', + title: 'Welcome to Heady Admin', + message: 'All systems operational. Press Ctrl+K to open command palette.', + duration: 5000 + }); + } + }, 1000); + // Log system info console.log('📊 Dashboard loaded successfully'); console.log('⚡ Real-time updates active'); console.log('🎨 Animations enabled'); + console.log('⌨️ Press Ctrl/Cmd+K for command palette'); + console.log('⌨️ Press ? for keyboard shortcuts'); }); // Handle visibility change diff --git a/HeadySystems_v13/apps/heady_admin_ui/js/shortcuts.js b/HeadySystems_v13/apps/heady_admin_ui/js/shortcuts.js new file mode 100644 index 00000000..668bcb09 --- /dev/null +++ b/HeadySystems_v13/apps/heady_admin_ui/js/shortcuts.js @@ -0,0 +1,165 @@ +/** + * Heady Admin UI - Keyboard Shortcuts Modal + * Display all available keyboard shortcuts in a categorized modal + */ + +class ShortcutsModal { + constructor() { + this.isOpen = false; + this.shortcuts = this.initShortcuts(); + this.createModal(); + this.bindEvents(); + } + + initShortcuts() { + return { + 'Navigation': [ + { keys: ['Ctrl/Cmd', 'K'], description: 'Open command palette' }, + { keys: ['Alt', 'D'], description: 'Go to Dashboard' }, + { keys: ['Alt', 'V'], description: 'Go to Verticals' }, + { keys: ['Alt', 'G'], description: 'Go to Governance' }, + { keys: ['Alt', 'A'], description: 'Go to Activity' } + ], + 'Actions': [ + { keys: ['Alt', 'T'], description: 'Toggle theme (Dark/Light)' }, + { keys: ['Alt', 'N'], description: 'Open notifications' }, + { keys: ['?'], description: 'Show keyboard shortcuts (this dialog)' } + ], + 'Modals': [ + { keys: ['Escape'], description: 'Close any modal or dialog' }, + { keys: ['↑', '↓'], description: 'Navigate command palette items' }, + { keys: ['Enter'], description: 'Select/Execute command' } + ] + }; + } + + createModal() { + const modal = document.createElement('div'); + modal.id = 'shortcuts-modal'; + modal.className = 'modal-overlay'; + modal.setAttribute('role', 'dialog'); + modal.setAttribute('aria-modal', 'true'); + modal.setAttribute('aria-labelledby', 'shortcuts-modal-title'); + modal.style.display = 'none'; + + let sectionsHTML = ''; + Object.entries(this.shortcuts).forEach(([category, shortcuts]) => { + sectionsHTML += ` +
+

${category}

+
+ ${shortcuts.map(shortcut => ` +
+
+ ${shortcut.keys.map(key => `${key}`).join(' + ')} +
+
${shortcut.description}
+
+ `).join('')} +
+
+ `; + }); + + modal.innerHTML = ` + + `; + + document.body.appendChild(modal); + this.modal = modal; + this.closeBtn = modal.querySelector('.modal-close'); + } + + bindEvents() { + // ? key to open shortcuts modal + document.addEventListener('keydown', (e) => { + // Only trigger if not in an input field + if (e.key === '?' && !this.isInputFocused()) { + e.preventDefault(); + this.toggle(); + } + + // Escape to close + if (e.key === 'Escape' && this.isOpen) { + e.preventDefault(); + this.close(); + } + }); + + // Click close button + if (this.closeBtn) { + this.closeBtn.addEventListener('click', () => { + this.close(); + }); + } + + // Click outside to close + this.modal.addEventListener('click', (e) => { + if (e.target === this.modal) { + this.close(); + } + }); + } + + isInputFocused() { + const activeElement = document.activeElement; + return activeElement && ( + activeElement.tagName === 'INPUT' || + activeElement.tagName === 'TEXTAREA' || + activeElement.isContentEditable + ); + } + + open() { + this.isOpen = true; + this.modal.style.display = 'flex'; + + // Prevent body scroll + document.body.style.overflow = 'hidden'; + + // Focus the modal for accessibility + setTimeout(() => { + this.modal.focus(); + }, 100); + } + + close() { + this.isOpen = false; + this.modal.style.display = 'none'; + document.body.style.overflow = ''; + } + + toggle() { + if (this.isOpen) { + this.close(); + } else { + this.open(); + } + } +} + +// Initialize shortcuts modal when DOM is ready +if (typeof window !== 'undefined') { + window.addEventListener('DOMContentLoaded', () => { + window.shortcutsModal = new ShortcutsModal(); + }); +} diff --git a/HeadySystems_v13/apps/heady_admin_ui/js/toast.js b/HeadySystems_v13/apps/heady_admin_ui/js/toast.js new file mode 100644 index 00000000..8e903ab1 --- /dev/null +++ b/HeadySystems_v13/apps/heady_admin_ui/js/toast.js @@ -0,0 +1,139 @@ +/** + * Heady Admin UI - Toast Notification System + * Modern toast notifications with auto-dismiss and stacking + */ + +class ToastSystem { + constructor() { + this.toasts = []; + this.container = null; + this.createContainer(); + } + + createContainer() { + const container = document.createElement('div'); + container.id = 'toast-container'; + container.className = 'toast-container'; + container.setAttribute('aria-live', 'polite'); + container.setAttribute('aria-atomic', 'true'); + document.body.appendChild(container); + this.container = container; + } + + /** + * Show a toast notification + * @param {Object} options - Toast options + * @param {string} options.type - Toast type: 'success', 'error', 'warning', 'info' + * @param {string} options.title - Toast title + * @param {string} options.message - Toast message + * @param {number} [options.duration=5000] - Auto-dismiss duration in ms (0 = no auto-dismiss) + */ + showToast({ type = 'info', title, message, duration = 5000 }) { + const toastId = `toast-${Date.now()}-${Math.random()}`; + + const toast = document.createElement('div'); + toast.id = toastId; + toast.className = `toast toast-${type} toast-enter`; + toast.setAttribute('role', 'alert'); + toast.setAttribute('aria-live', 'assertive'); + + const iconMap = { + success: '✓', + error: '✕', + warning: '⚠', + info: 'ℹ' + }; + + const icon = iconMap[type] || 'ℹ'; + + toast.innerHTML = ` +
${icon}
+
+
${this.escapeHtml(title)}
+ ${message ? `
${this.escapeHtml(message)}
` : ''} +
+ + ${duration > 0 ? '
' : ''} + `; + + // Add to container + this.container.appendChild(toast); + + // Trigger animation + requestAnimationFrame(() => { + toast.classList.remove('toast-enter'); + }); + + // Setup close button + const closeBtn = toast.querySelector('.toast-close'); + closeBtn.addEventListener('click', () => { + this.dismissToast(toastId); + }); + + // Setup auto-dismiss + if (duration > 0) { + const progressBar = toast.querySelector('.toast-progress-bar'); + if (progressBar) { + // Animate progress bar + progressBar.style.transition = `width ${duration}ms linear`; + requestAnimationFrame(() => { + progressBar.style.width = '0%'; + }); + } + + // Auto-dismiss after duration + setTimeout(() => { + this.dismissToast(toastId); + }, duration); + } + + // Track toast + this.toasts.push({ + id: toastId, + element: toast, + type, + title, + message, + duration + }); + + return toastId; + } + + dismissToast(toastId) { + const toastData = this.toasts.find(t => t.id === toastId); + if (!toastData) return; + + const toast = toastData.element; + + // Exit animation + toast.classList.add('toast-exit'); + + // Remove after animation + setTimeout(() => { + if (toast.parentNode) { + toast.parentNode.removeChild(toast); + } + // Remove from tracking + this.toasts = this.toasts.filter(t => t.id !== toastId); + }, 300); + } + + dismissAll() { + const toastIds = this.toasts.map(t => t.id); + toastIds.forEach(id => this.dismissToast(id)); + } + + escapeHtml(text) { + const div = document.createElement('div'); + div.textContent = text; + return div.innerHTML; + } +} + +// Initialize toast system when DOM is ready +if (typeof window !== 'undefined') { + window.addEventListener('DOMContentLoaded', () => { + window.toastSystem = new ToastSystem(); + }); +} diff --git a/HeadySystems_v13/apps/heady_admin_ui/skeleton-demo.html b/HeadySystems_v13/apps/heady_admin_ui/skeleton-demo.html new file mode 100644 index 00000000..a45ebde9 --- /dev/null +++ b/HeadySystems_v13/apps/heady_admin_ui/skeleton-demo.html @@ -0,0 +1,110 @@ + + + + + + Skeleton Loading Demo - Heady Admin + + + + + +
+

Skeleton Loading States Demo

+

Examples of skeleton loading components for the Heady Admin Dashboard

+ + +
+

Skeleton Text

+
+
+
+
+
+ + +
+

Skeleton Cards

+
+
+
+
+
+
+
+
+
+
+
+
+ +
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + +
+

Skeleton Chart

+
+
+
+
+
+
+
+
+
+
+
+ + +
+

Skeleton List

+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +