Repository: puikinsh/Adminator-admin-dashboard
Branch: master
Commit: 020731181798
Files: 174
Total size: 1.1 MB
Directory structure:
gitextract_t4ripph3/
├── .babelrc
├── .claude/
│ └── settings.local 2.json
├── .editorconfig
├── .gitattributes
├── .github/
│ ├── ISSUE_TEMPLATE.md
│ └── workflows/
│ ├── merge.yml
│ └── release.yml
├── .gitignore
├── .npmignore
├── .nvmrc
├── .stylelintrc.json
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── IMPROVEMENT_PLAN.md
├── LICENSE
├── README.md
├── browserslist
├── ci/
│ ├── getVersion.sh
│ └── verifyVersion.sh
├── docs/
│ ├── COMPONENT_GUIDE.md
│ ├── README.md
│ ├── _config.yml
│ ├── _sass/
│ │ └── custom.scss
│ ├── api/
│ │ └── theme-api.md
│ ├── api.md
│ ├── assets/
│ │ └── images/
│ │ ├── adminator-dark-mode.avif
│ │ └── adminator-light-mode.avif
│ ├── customization/
│ │ └── theme-system.md
│ ├── customization.md
│ ├── examples/
│ │ └── theme-integration.md
│ ├── examples.md
│ ├── getting-started/
│ │ ├── build-deployment.md
│ │ ├── development.md
│ │ ├── installation.md
│ │ └── project-structure.md
│ ├── getting-started.md
│ └── index.md
├── eslint.config.mjs
├── jsconfig.json
├── package.json
├── src/
│ ├── 404.html
│ ├── 500.html
│ ├── assets/
│ │ ├── scripts/
│ │ │ ├── app.js
│ │ │ ├── charts/
│ │ │ │ ├── chartJS/
│ │ │ │ │ └── index.js
│ │ │ │ ├── easyPieChart/
│ │ │ │ │ └── index.js
│ │ │ │ ├── index.js
│ │ │ │ └── sparkline/
│ │ │ │ └── index.js
│ │ │ ├── chat/
│ │ │ │ └── index.js
│ │ │ ├── components/
│ │ │ │ ├── Chart.js
│ │ │ │ └── Sidebar.js
│ │ │ ├── constants/
│ │ │ │ └── colors.js
│ │ │ ├── datatable/
│ │ │ │ └── index.js
│ │ │ ├── datepicker/
│ │ │ │ └── index.js
│ │ │ ├── email/
│ │ │ │ └── index.js
│ │ │ ├── fullcalendar/
│ │ │ │ └── index.js
│ │ │ ├── googleMaps/
│ │ │ │ └── index.js
│ │ │ ├── index.js
│ │ │ ├── masonry/
│ │ │ │ └── index.js
│ │ │ ├── popover/
│ │ │ │ └── index.js
│ │ │ ├── scrollbar/
│ │ │ │ └── index.js
│ │ │ ├── search/
│ │ │ │ └── index.js
│ │ │ ├── skycons/
│ │ │ │ └── index.js
│ │ │ ├── ui/
│ │ │ │ └── index.js
│ │ │ ├── utils/
│ │ │ │ ├── date.js
│ │ │ │ ├── dom.js
│ │ │ │ ├── events.js
│ │ │ │ ├── index.js
│ │ │ │ ├── logger.js
│ │ │ │ ├── performance.js
│ │ │ │ ├── sanitize.js
│ │ │ │ ├── storage.js
│ │ │ │ └── theme.js
│ │ │ └── vectorMaps/
│ │ │ └── index.js
│ │ ├── static/
│ │ │ └── fonts/
│ │ │ └── icons/
│ │ │ └── fontawesome/
│ │ │ └── FontAwesome.otf
│ │ └── styles/
│ │ ├── index.scss
│ │ ├── spec/
│ │ │ ├── components/
│ │ │ │ ├── easyPieChart.scss
│ │ │ │ ├── footer.scss
│ │ │ │ ├── forms.scss
│ │ │ │ ├── index.scss
│ │ │ │ ├── loader.scss
│ │ │ │ ├── masonry.scss
│ │ │ │ ├── modernize.scss
│ │ │ │ ├── pageContainer.scss
│ │ │ │ ├── progressBar.scss
│ │ │ │ ├── sidebar.scss
│ │ │ │ └── topbar.scss
│ │ │ ├── generic/
│ │ │ │ ├── base.scss
│ │ │ │ └── index.scss
│ │ │ ├── index.scss
│ │ │ ├── screens/
│ │ │ │ ├── chat.scss
│ │ │ │ ├── email.scss
│ │ │ │ └── index.scss
│ │ │ ├── settings/
│ │ │ │ ├── baseColors.scss
│ │ │ │ ├── borders.scss
│ │ │ │ ├── breakpoints.scss
│ │ │ │ ├── fonts.scss
│ │ │ │ ├── index.scss
│ │ │ │ └── materialColors.scss
│ │ │ ├── tools/
│ │ │ │ ├── index.scss
│ │ │ │ └── mixins/
│ │ │ │ ├── clearfix.scss
│ │ │ │ ├── index.scss
│ │ │ │ ├── mediaQueriesRanges.scss
│ │ │ │ └── placeholder.scss
│ │ │ └── utils/
│ │ │ ├── colors.scss
│ │ │ ├── index.scss
│ │ │ └── layout/
│ │ │ ├── helpers/
│ │ │ │ ├── border.scss
│ │ │ │ ├── flex.scss
│ │ │ │ ├── index.scss
│ │ │ │ ├── layout.scss
│ │ │ │ ├── lists.scss
│ │ │ │ ├── margin.scss
│ │ │ │ ├── objects.scss
│ │ │ │ ├── padding.scss
│ │ │ │ ├── positions.scss
│ │ │ │ ├── pseudo.scss
│ │ │ │ ├── sizes.scss
│ │ │ │ └── typography.scss
│ │ │ ├── index.scss
│ │ │ ├── mixins/
│ │ │ │ ├── generateResponsive.scss
│ │ │ │ ├── index.scss
│ │ │ │ └── mediaQueryCondition.scss
│ │ │ └── utils/
│ │ │ ├── center.scss
│ │ │ ├── gap.scss
│ │ │ ├── index.scss
│ │ │ ├── layers.scss
│ │ │ └── peers.scss
│ │ ├── utils/
│ │ │ ├── mobile.scss
│ │ │ └── theme.css
│ │ └── vendor/
│ │ ├── datepicker.scss
│ │ ├── font-awesome.css
│ │ ├── fullcalendar.scss
│ │ ├── index.scss
│ │ ├── jquery.datatables.scss
│ │ ├── perfectScrollbar.scss
│ │ ├── sparkline.scss
│ │ └── themify-icons.css
│ ├── basic-table.html
│ ├── blank.html
│ ├── buttons.html
│ ├── calendar.html
│ ├── charts.html
│ ├── chat.html
│ ├── compose.html
│ ├── datatable.html
│ ├── email.html
│ ├── forms.html
│ ├── google-maps.html
│ ├── index.html
│ ├── signin.html
│ ├── signup.html
│ ├── ui.html
│ └── vector-maps.html
├── tests/
│ ├── setup.js
│ └── utils/
│ ├── dom.test.js
│ ├── logger.test.js
│ └── theme.test.js
├── vitest.config.js
├── webpack/
│ ├── config.js
│ ├── devServer.js
│ ├── manifest.js
│ ├── plugins/
│ │ ├── caseSensitivePlugin.js
│ │ ├── copyPlugin.js
│ │ ├── dashboardPlugin.js
│ │ ├── extractPlugin.js
│ │ ├── htmlPlugin.js
│ │ ├── index.js
│ │ └── internal.js
│ └── rules/
│ ├── css.js
│ ├── fonts.js
│ ├── images.js
│ ├── index.js
│ ├── js.js
│ └── sass.js
└── webpack.config.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .babelrc
================================================
{
"presets": [
[
"@babel/preset-env",
{
"useBuiltIns": false,
"modules": false
}
]
],
"plugins": ["@babel/plugin-transform-runtime"]
}
================================================
FILE: .claude/settings.local 2.json
================================================
{
"permissions": {
"allow": [
"Bash(npm run build:*)",
"Bash(npm install)",
"Bash(npm run lint)",
"Bash(rm:*)",
"Bash(ls:*)",
"Bash(pkill:*)",
"Bash(true)",
"Bash(npm start)",
"Bash(grep:*)",
"Bash(sudo rm:*)",
"Bash(npx eslint:*)",
"Bash(npm run lint:*)",
"Bash(gh release create:*)",
"Bash(npm search:*)",
"Bash(npm pack:*)",
"Bash(npm:*)",
"WebFetch(domain:keenthemes.com)"
],
"deny": []
}
}
================================================
FILE: .editorconfig
================================================
# -----------------------------
# General
# -----------------------------
# EditorConfig helps developers define and maintain consistent
# coding styles between different editors and IDEs
# editorconfig.org
# top-most EditorConfig file
root = true
# -----------------------------
# All Files
# -----------------------------
[*]
end_of_line = lf
charset = utf-8
insert_final_newline = true
trim_trailing_whitespace = true
indent_style = space
indent_size = 2
# -----------------------------
# Markdown Files
# -----------------------------
[*.md]
trim_trailing_whitespace = false
================================================
FILE: .gitattributes
================================================
# -----------------------------
# General
# -----------------------------
# AUTO-DETECT - Handle line endings automatically for files detected
# as text and leave all files detected as binary untouched.
# This will handle all files NOT defined below.
* text=auto
# -----------------------------
# Source Code
# -----------------------------
*.css text
*.html text
*.js text
*.json text
*.scss text
# -----------------------------
# Documentation
# -----------------------------
*.md text
CHANGELOG text
LICENSE text
# -----------------------------
# Configs
# -----------------------------
.editorconfig text
.gitattributes text
.gitconfig text
.gitignore text
.babelrc text
.stylelintrc text
.eslintrc text
.yarnclean text
*.yml text
browserlist text
yarn.lock text
# -----------------------------
# Graphics
# -----------------------------
*.gif binary
*.ico binary
*.jpg binary
*.jpeg binary
*.png binary
*.svg text
# -----------------------------
# Fonts
# -----------------------------
*.ttf binary
*.eot binary
*.otf binary
*.woff binary
*.woff2 binary
================================================
FILE: .github/ISSUE_TEMPLATE.md
================================================
```
### 2.2 Navigation Accessibility
**Missing ARIA attributes on dropdowns:**
```html
```
### 2.3 DataTable Accessibility
**Missing semantic markup:**
```html
```
### 2.4 Chart Accessibility
**Canvas elements need descriptive labels:**
```html
```
### 2.5 Mobile Search Overlay
**Keyboard accessibility needed:**
- Trap focus within overlay when open
- Close on Escape key press
- Return focus to trigger when closed
---
## Phase 3: Error Handling & Robustness
### 3.1 Add Development Logging
Create a logging utility that only logs in development:
```javascript
// src/assets/scripts/utils/logger.js
const Logger = {
isDev: process.env.NODE_ENV === 'development',
warn(message, context) {
if (this.isDev) console.warn(`[Adminator] ${message}`, context);
},
error(message, context) {
if (this.isDev) console.error(`[Adminator] ${message}`, context);
}
};
```
### 3.2 Replace Silent Failures
**Current pattern (problematic):**
```javascript
} catch {
// Silent failure
}
```
**Improved pattern:**
```javascript
} catch (error) {
Logger.warn('Feature X failed to initialize', { error });
}
```
### 3.3 Add Input Validation
Validate inputs in public API methods:
- Theme.apply() - validate theme name
- Chart initialization - validate canvas elements
- DateUtils - validate date formats
---
## Phase 4: Performance Optimizations
### 4.1 Replace Clone-and-Replace Pattern
**Current (inefficient):**
```javascript
// Clones entire DOM node to remove listeners
const newItem = item.cloneNode(true);
item.parentNode.replaceChild(newItem, item);
```
**Improved (use AbortController):**
```javascript
const controller = new AbortController();
element.addEventListener('click', handler, { signal: controller.signal });
// Later: controller.abort() to remove listener
```
### 4.2 Use Event Delegation
**Instead of:**
```javascript
document.querySelectorAll('.dropdown').forEach(el => {
el.addEventListener('click', handler);
});
```
**Use:**
```javascript
document.addEventListener('click', (e) => {
const dropdown = e.target.closest('.dropdown');
if (dropdown) handler(e);
});
```
### 4.3 Lazy Load Charts
Only initialize charts when they're visible:
```javascript
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
initChart(entry.target);
observer.unobserve(entry.target);
}
});
});
```
### 4.4 Use ResizeObserver Instead of Window Resize
```javascript
const resizeObserver = new ResizeObserver(entries => {
entries.forEach(entry => {
// Handle resize
});
});
resizeObserver.observe(chartContainer);
```
---
## Phase 5: Code Quality & Consistency
### 5.1 Consistent Export Pattern
Standardize all modules to use named exports:
```javascript
// Consistent pattern
export { Theme };
export { DOM };
export { DateUtils };
```
### 5.2 Add JSDoc Documentation
```javascript
/**
* Toggles the application theme between light and dark mode
* @fires adminator:themeChanged
* @returns {string} The new theme ('light' or 'dark')
*/
function toggle() {
// ...
}
```
### 5.3 Consolidate Dark Mode SCSS
Move dark mode overrides from `index.scss` (800+ lines) to dedicated partial:
```
src/assets/styles/spec/utils/
├── _theme.scss (CSS variables)
└── _darkmode.scss (dark mode overrides)
```
### 5.4 Reduce !important Usage
Current: 16 instances of `!important` in index.scss
Fix specificity issues at the source rather than using `!important`:
- Review selector specificity
- Use CSS custom properties for overridable values
- Consider CSS layers for proper cascade
---
## Phase 6: Testing Infrastructure
### 6.1 Set Up Testing Framework
```bash
npm install -D vitest @testing-library/dom jsdom
```
### 6.2 Priority Test Files
1. **Utils testing:**
- `tests/utils/dom.test.js` - DOM utilities
- `tests/utils/theme.test.js` - Theme switching
- `tests/utils/date.test.js` - Date formatting
2. **Component testing:**
- `tests/components/Sidebar.test.js`
- `tests/components/Chart.test.js`
3. **Integration testing:**
- `tests/integration/theme-persistence.test.js`
- `tests/integration/mobile-menu.test.js`
### 6.3 Add Test Script
```json
{
"scripts": {
"test": "vitest",
"test:coverage": "vitest --coverage"
}
}
```
---
## Phase 7: Documentation Improvements
### 7.1 Add Component Development Guide
Create `docs/COMPONENT_GUIDE.md`:
- How to add new components
- Theme integration requirements
- Event patterns to follow
- Mobile considerations
### 7.2 Add API Documentation
Document all public APIs with examples:
- Theme API (toggle, apply, current)
- DOM utilities (select, addClass, etc.)
- Custom events (adminator:ready, adminator:themeChanged)
### 7.3 Add Migration Guide
For users upgrading from jQuery version:
- Breaking changes
- API differences
- Migration steps
### 7.4 Inline Code Documentation
Add JSDoc to all exported functions and classes.
---
## Phase 8: Developer Experience
### 8.1 Add TypeScript Declarations
Create `types/adminator.d.ts` for IDE support without full TypeScript migration:
```typescript
declare namespace Adminator {
interface Theme {
current(): 'light' | 'dark';
toggle(): void;
apply(theme: 'light' | 'dark'): void;
}
}
```
### 8.2 Add VS Code Settings
Create `.vscode/settings.json`:
```json
{
"editor.formatOnSave": true,
"css.lint.unknownAtRules": "ignore",
"scss.lint.unknownAtRules": "ignore"
}
```
### 8.3 Add Recommended Extensions
Create `.vscode/extensions.json`:
```json
{
"recommendations": [
"dbaeumer.vscode-eslint",
"stylelint.vscode-stylelint",
"editorconfig.editorconfig"
]
}
```
---
## Phase 9: Security Hardening
### 9.1 Wrap localStorage Operations
```javascript
const Storage = {
get(key) {
try {
return localStorage.getItem(key);
} catch {
return null; // Private browsing or quota exceeded
}
},
set(key, value) {
try {
localStorage.setItem(key, value);
return true;
} catch {
return false;
}
}
};
```
### 9.2 Add Content Security Policy Headers
Document recommended CSP headers for production deployment.
### 9.3 Google Maps API Key Handling
Add documentation for proper API key restrictions.
---
## Phase 10: Build & Bundle Optimization
### 10.1 Analyze Bundle Size
Add bundle analysis:
```json
{
"scripts": {
"analyze": "webpack --profile --json > stats.json && webpack-bundle-analyzer stats.json"
}
}
```
### 10.2 Code Splitting
Consider splitting large chunks:
- Charts (Chart.js) - lazy load
- FullCalendar - lazy load on calendar page
- Vector Maps - lazy load on maps page
### 10.3 Remove Unused Lodash Imports
Replace full lodash with specific imports:
```javascript
// Instead of
import _ from 'lodash';
// Use
import debounce from 'lodash/debounce';
```
---
## Implementation Priority Order
### Week 1: Critical (Must Have)
1. Remove dead code (Phase 1.1)
2. Fix HTML lang attributes (Phase 1.2)
3. Remove unused dependencies (Phase 1.1)
### Week 2: High Priority
4. Theme toggle accessibility (Phase 2.1)
5. Add development logging (Phase 3.1)
6. Replace silent failures (Phase 3.2)
### Week 3: Medium Priority
7. DataTable accessibility (Phase 2.3)
8. Event delegation refactor (Phase 4.2)
9. JSDoc documentation (Phase 5.2)
### Week 4: Enhancement
10. Testing infrastructure (Phase 6)
11. Component development guide (Phase 7.1)
12. TypeScript declarations (Phase 8.1)
---
## Files Changed Summary
### Delete (3 files)
- `src/assets/scripts/app 2.js`
- `src/assets/scripts/sidebar/index.js`
- `tsconfig.json` (optional, or commit to TS)
### Create (8+ files)
- `src/assets/scripts/utils/logger.js`
- `src/assets/styles/spec/utils/_darkmode.scss`
- `docs/COMPONENT_GUIDE.md`
- `docs/API.md`
- `docs/MIGRATION.md`
- `types/adminator.d.ts`
- `.vscode/settings.json`
- `.vscode/extensions.json`
### Modify (25+ files)
- All 18 HTML files (add `lang="en"`)
- `package.json` (remove unused deps, add test script)
- `src/assets/scripts/app.js` (error handling, cleanup)
- `src/assets/scripts/utils/theme.js` (accessibility)
- `src/assets/styles/spec/index.scss` (refactor dark mode)
- Component files (JSDoc, accessibility)
---
## Success Metrics
After implementing these improvements:
1. **Lighthouse Accessibility Score**: Target 90+
2. **Bundle Size**: Reduce by 10-15% (remove lodash full, unused deps)
3. **Test Coverage**: Achieve 60%+ on utilities
4. **Documentation**: Complete API reference
5. **Zero Dead Code**: All files actively used
6. **Developer Experience**: Full IDE support with types
---
## Notes for Freelance Developers
This template will be excellent for freelance developers after these improvements because:
1. **Clean, Maintainable Code**: No dead code, consistent patterns
2. **Accessible by Default**: WCAG compliance out of the box
3. **Well-Documented**: Easy to understand and extend
4. **Type-Safe Development**: IDE autocomplete and error detection
5. **Tested Foundation**: Confidence when making changes
6. **Modern Stack**: No legacy jQuery baggage
7. **Performance Optimized**: Fast load times, efficient updates
---
*Document created: January 2026*
*Template version: 2.9.0*
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2018 Aigars Silkalns
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# Adminator Bootstrap 5 Admin Template v3.0.0
**Adminator** is a responsive Bootstrap 5 Admin Template built with modern development tools. It provides you with a collection of ready to use code snippets and utilities, custom pages, a collection of applications and some useful widgets.
**Latest Update (v3.0.0)**: Major architecture release with new utility modules (Events, Performance, Storage, Sanitize, Logger), testing infrastructure (Vitest), code splitting, bundle analyzer, and comprehensive documentation. Zero jQuery, zero lodash - pure vanilla JavaScript.
**Looking for more premium admin templates?** Visit **[DashboardPack.com](https://dashboardpack.com/)** for a curated collection of high-quality admin dashboard templates for various frameworks and technologies.
**Performance Benefits**: Faster load times, optimized code splitting, modern ES6+ utilities, and zero external library overhead.
**[Complete Documentation](https://puikinsh.github.io/Adminator-admin-dashboard/)** - Detailed setup guides, API reference, and examples
Preview of this awesome admin template available here: https://colorlib.com/polygon/adminator/index.html
# Preview
### Screenshots
**Light Mode:**

**Dark Mode:**

### Demo Site: [Here](https://colorlib.com/polygon/adminator/index.html)
## Upgrade to a Premium Dashboard
Need advanced features, dedicated support, and production-ready code? Explore our handpicked collection of professional admin templates on [DashboardPack](https://dashboardpack.com/?utm_source=github&utm_medium=readme&utm_campaign=adminator).
TailPanel
React + TypeScript + Tailwind CSS + Vite. 9 dashboard designs, dark and light themes.
Admindek
Bootstrap 5 + vanilla JS. 100+ components, dark/light modes, RTL support, 10 color presets.
Adminty
Bootstrap 5. 160+ ready-made pages, full UI component library for rapid development.
ArchitectUI
Bootstrap 5. 250+ components, modular architecture, 9 unique dashboard layouts.
Kero
Bootstrap 5 + Webpack. Dual layout system (horizontal + sidebar), SASS theming.
Cryptocurrency Dashboard
Bootstrap. Built specifically for ICO platforms, Bitcoin tracking, and crypto portfolios.
View All Premium Templates
## TOC
- [What's New in v3.0.0](#whats-new-in-v300)
- [Getting Started](#getting-started)
- [Prerequisites](#prerequisites)
- [Installing & Local Development](#installing--local-development)
- [Adminator for other platforms and frameworks](#adminator-for-other-platforms-and-frameworks)
- [Files/Folder Structure](#filesfolders-structure)
- [Deployment](#deployment)
- [Built With](#built-with)
- [Changelog](#changelog)
- [Authors](#authors)
- [License](#license)
## What's New in v3.0.0
### Major Architecture & Developer Experience Release
This release represents a comprehensive overhaul adding professional-grade utilities, testing, security, and optimized builds.
### New Utility Modules
- **Events** - Event delegation, debounce, throttle (replaces lodash)
- **Performance** - ResizeObserver, IntersectionObserver, lazy loading utilities
- **Storage** - Safe localStorage wrapper with in-memory fallback
- **Sanitize** - HTML/input sanitization for XSS prevention
- **Logger** - Development-only logging utility
### Testing Infrastructure
- **Vitest** - Modern, fast testing framework
- **Coverage Reports** - V8-based code coverage via `npm run test:coverage`
- **Test Suites** - Initial tests for theme, DOM, and logger utilities
### Build & Bundle Optimization
- **Code Splitting** - Separate chunks for Chart.js (529KB), FullCalendar (654KB), Bootstrap
- **Bundle Analyzer** - New `npm run build:analyze` for visual inspection
- **Console Removal** - TerserPlugin drops console/debugger in production
- **Lodash Removed** - Custom Events utility saves ~70KB
### Documentation & DX
- **API Reference** - Complete docs in `docs/API.md`
- **Component Guide** - Development patterns in `docs/COMPONENT_GUIDE.md`
- **TypeScript Declarations** - IDE support via `types/adminator.d.ts`
- **VSCode Settings** - Project-specific editor configuration
### Code Quality
- **Dead Code Removed** - Cleaned up unused files
- **HTML Accessibility** - Added `lang="en"` to all 18 HTML pages
- **Zero Vulnerabilities** - Full security audit passed
### Previous Updates (v2.9.x)
- Comprehensive dependency updates to latest versions
- Enhanced SCSS linting with stylelint-config-standard-scss
- Security vulnerability fixes (node-forge, js-yaml)
## What's New in v2.7.1
**jQuery-Free Release** - Complete removal of jQuery dependency with modern vanilla JavaScript:
### Major Performance Improvements
- **~600KB Bundle Reduction**: Eliminated jQuery and all jQuery-dependent plugins
- **Faster Load Times**: Native DOM manipulation for optimal performance
- **Smaller Bundle Size**: Significantly reduced JavaScript payload
- **Modern ES6+ Code**: Class-based architecture with modern JavaScript features
### jQuery Replacements (Zero Breaking Changes)
- **Chart.js**: Replaced jQuery Sparkline with Chart.js mini charts
- **HTML5 Date Pickers**: Enhanced native date inputs with Day.js support
- **Vanilla DataTables**: Custom table component with sorting and pagination
- **SVG Pie Charts**: Pure JavaScript circular progress indicators
- **Vector Maps**: JavaScript-based world map with markers and interactions
- **Vanilla Popovers**: Lightweight alternatives to Bootstrap JS components
### Technical Achievements
- **100% Vanilla JavaScript**: No jQuery dependency anywhere in the codebase
- **Component Architecture**: Modern class-based components (Sidebar, Charts, etc.)
- **Enhanced DOM Utilities**: jQuery-like functionality using native JavaScript
- **Mobile Optimized**: Touch-friendly interactions and responsive behavior
- **Theme Integration**: All new components fully support dark/light mode switching
### Previous Updates (v2.6.0 - Dark Mode System)
### Dark Mode Features
- **Smart Theme Toggle**: Bootstrap-based switch with sun/moon icons and intuitive labels
- **OS Preference Detection**: Automatically detects and applies your preferred color scheme
- **Persistent Storage**: Remembers your theme choice across browser sessions
- **Instant Switching**: Real-time theme updates without page reload
- **Component Integration**: All charts, calendars, maps, and UI elements are theme-aware
### Technical Implementation
- **CSS Variables Architecture**: Comprehensive color system with semantic naming
- **Chart.js Integration**: Dynamic color schemes for all chart types
- **FullCalendar Support**: Dark-mode aware calendar with proper contrast
- **Vector Maps**: Custom color palettes for both light and dark themes
- **Component Compatibility**: Theme support across all interactive elements
### Previous Updates (v2.5.0)
- **Latest Dependencies**: All 22+ dependencies updated to latest versions
- **Modern Build Tools**: webpack 5.99.9, webpack-dev-server 5.2.2
- **ESLint 9.x**: Migrated to modern flat config format
- **Enhanced CSS**: Latest Sass (1.89.2), PostCSS (8.5.6), Bootstrap (5.3.7)
- **Updated Components**: Chart.js 4.5.0, FullCalendar 6.1.17
- **Zero Vulnerabilities**: Complete security audit with all packages secure
## Getting Started
You can use **Adminator** in several ways:
### NPM Package Installation (Recommended)
Install the complete template as an npm package:
```bash
# Install via npm
npm install adminator-admin-dashboard
# Or install via yarn
yarn add adminator-admin-dashboard
```
**Package Information:**
- **Package Name**: `adminator-admin-dashboard`
- **Version**: 3.0.0 (Architecture release)
- **Size**: 5.7 MB (includes source + built assets)
- **Registry**: https://www.npmjs.com/package/adminator-admin-dashboard
**Usage after npm install:**
```bash
# Navigate to the package directory
cd node_modules/adminator-admin-dashboard
# Install development dependencies (if you want to customize)
npm install
# Start development server
npm start
```
**What's included in the npm package:**
- Complete source code (`src/` directory)
- Pre-built production assets (`dist/` directory)
- All dependencies and development tools
- Documentation (CLAUDE.md, CHANGELOG.md)
- Ready-to-use HTML templates
### Local Development Setup
For development and customization, clone the repository:
#### Prerequisites
- **Node.js 18.12.0 or higher** (tested with Node.js 23.11.0)
- **npm** (included with Node.js) or **Yarn**
- **Git**
#### Installing & Local Development
```bash
# Clone the repository
git clone https://github.com/puikinsh/Adminator-admin-dashboard.git adminator
# Navigate to the project directory
cd adminator
# Install dependencies
npm install
# Start development server (available at http://localhost:4000)
npm start
# Alternative: Start with webpack dashboard
npm run dev
```
### Quick Start Options
1. **Fastest**: Use prebuilt static assets from [releases](https://github.com/puikinsh/Adminator-admin-dashboard/releases)
2. **Recommended**: Install via npm package for easy updates
3. **Development**: Clone repository for full customization
#### Development Commands
```bash
# Development server with hot reload
npm start
# Development server with dashboard
npm run dev
# Build for production (optimized)
npm run build
# Build for production (unminified)
npm run release:unminified
# Build for production (minified)
npm run release:minified
# Preview production build
npm run preview
# Lint JavaScript files
npm run lint:js
# Lint SCSS files
npm run lint:scss
# Run all linters
npm run lint
# Run tests
npm run test
# Run tests with coverage
npm run test:coverage
# Analyze bundle size
npm run build:analyze
```
## Dark Mode Usage
Adminator now includes a comprehensive dark mode system that works out of the box:
### Automatic Setup
- Dark mode is automatically initialized on page load
- Detects your OS preference (light/dark) on first visit
- Remembers your choice across browser sessions
### Theme Toggle
- Look for the **Light/Dark** toggle switch in the header navigation
- Click to instantly switch between light and dark themes
- Visual feedback with sun and moon icons
### For Developers
**[Complete Theme API Documentation →](https://puikinsh.github.io/Adminator-admin-dashboard/api/theme-api)**
**Using the Theme API:**
```javascript
// Get current theme
const currentTheme = Theme.current(); // 'light' or 'dark'
// Switch themes programmatically
Theme.toggle();
// Set specific theme
Theme.apply('dark');
// Listen for theme changes
window.addEventListener('adminator:themeChanged', (event) => {
console.log('Theme changed to:', event.detail.theme);
});
```
**CSS Variables for Custom Styling:**
```css
.my-component {
background: var(--c-bkg-card);
color: var(--c-text-base);
border: 1px solid var(--c-border);
}
```
**Available CSS Variables:**
- `--c-bkg-body` - Main background
- `--c-bkg-card` - Card backgrounds
- `--c-text-base` - Primary text color
- `--c-text-muted` - Secondary text color
- `--c-border` - Border colors
- `--c-primary` - Primary brand color
**[View Complete CSS Variables Reference →](https://puikinsh.github.io/Adminator-admin-dashboard/customization/theme-system)**
## Documentation
**[Complete Documentation Site](https://puikinsh.github.io/Adminator-admin-dashboard/)** - Comprehensive guides and API reference
### Quick Links:
- **[Installation Guide](https://puikinsh.github.io/Adminator-admin-dashboard/getting-started/installation)** - Complete setup instructions
- **[Theme System](https://puikinsh.github.io/Adminator-admin-dashboard/customization/theme-system)** - Dark mode and theming
- **[API Reference](https://puikinsh.github.io/Adminator-admin-dashboard/api/theme-api)** - JavaScript API documentation
- **[Examples](https://puikinsh.github.io/Adminator-admin-dashboard/examples/theme-integration)** - Integration examples
## Adminator for other platforms and frameworks
* [Adminator right to left](https://github.com/mortezakarimi/Adminator-admin-dashboard-rtl) - Adminator modified to work with right to left languages like Persian and Arabic
## Files/Folders Structure
Here is a brief explanation of the template folder structure and some of its main files usage:
```
└── src # Contains all template source files.
│ └── assets # Contains JS, CSS, images and icon fonts.
│ │ └── scripts # Contains all JavaScript files.
│ │ │ └── charts # Chart.js, Sparkline & Pie Chart plugins init.
│ │ │ └── chat # All chat app JS code.
│ │ │ └── constants # Template constant values like color values.
│ │ │ └── datatable # Date table plugin init.
│ │ │ └── datepicker # Bootstrap datepicker init.
│ │ │ └── email # All email app code.
│ │ │ └── fullcalendar # Fullcalendar plugin init.
│ │ │ └── googleMaps # Google maps API integration code.
│ │ │ └── masonry # Masonry layout code.
│ │ │ └── popover # Bootstrap popover plugin init.
│ │ │ └── scrollbar # Perfect scrollbar plugin init.
│ │ │ └── search # Topbar toggle search init.
│ │ │ └── sidebar # Sidebar JS code.
│ │ │ └── skycons # Animated icons plugin init.
│ │ │ └── utils # Basic utils used for proper rendering.
│ │ │ └── vectorMaps # Vector maps plugin init.
│ │ │ └── app.js # Main application entry point.
│ │ │
│ │ └── static # Contains the non-code files.
│ │ │ └── fonts # Contains icon fonts.
│ │ │ └── images # Contains all template images/svg.
│ │ │
│ │ └── styles # Contains all SCSS files.
│ │ └── spec # Contains custom SCSS files.
│ │ │ └── components # Contains all template components.
│ │ │ └── generic # Contains basic scaffolding styles.
│ │ │ └── screens # Contains views specific styles.
│ │ │ └── settings # Contains all template variables.
│ │ │ └── tools # Contains all mixins.
│ │ │ └── utils # Contains helper classes.
│ │ │ └── index.scss # Indicator file.
│ │ │
│ │ └── vendor # Contains all plugin files & custom styles.
│ │ └── index.scss # Main style entry point.
│ │
│ └── *.html # All HTML template pages.
└── webpack # Contains Webpack configuration.
│ └── plugins # Contains all Webpack plugins config.
│ └── rules # Contains Webpack loaders config.
│ └── config.js # Main Webpack configuration.
│ └── devServer.js # Development server configuration.
│ └── manifest.js # Build system constants.
│
└── .babelrc # Babel ES6 transpiler configuration.
└── .editorconfig # Code editor consistency settings.
└── eslint.config.mjs # ESLint 9.x flat configuration.
└── .gitattributes # Git attributes configuration.
└── .gitignore # Git ignore patterns.
└── .stylelintrc.json # SCSS/CSS linting configuration.
└── browserslist # Supported browsers configuration.
└── CHANGELOG.md # Version history and updates.
└── package.json # Node.js package configuration.
└── README.md # This documentation file.
└── webpack.config.js # Webpack entry configuration.
```
## Deployment
In deployment process, you have several commands:
1. **Production Build** - Generate optimized assets for production:
```bash
npm run build
```
2. **Production Preview** - Preview the production build locally:
```bash
npm run preview
```
3. **Custom Builds**:
```bash
# Unminified production build (for debugging)
npm run release:unminified
# Minified production build (smallest size)
npm run release:minified
```
The built files will be available in the `dist/` directory.
## Built With
### Core Framework & Build Tools
- [Bootstrap 5.3.8](http://getbootstrap.com/) - Modern CSS framework
- [Webpack 5.103.0](https://webpack.js.org/) - Module bundler and build tool
- [Babel 7.28.x](https://babeljs.io/) - JavaScript transpiler
- [Sass 1.94.2](http://sass-lang.com/) - CSS preprocessor
- [PostCSS 8.5.6](http://postcss.org/) - CSS transformations
- [ESLint 9.39.1](https://eslint.org/) - JavaScript linting (flat config)
- [Stylelint 16.26.1](https://stylelint.io/) - CSS/SCSS linting
### UI Components & Charts
- [Chart.js 4.5.1](http://www.chartjs.org/) - Modern charting library
- [FullCalendar 6.1.19](https://fullcalendar.io/) - Interactive calendar
- [DataTables](https://datatables.net/) - Advanced table functionality
- [Easy Pie Chart](http://rendro.github.io/easy-pie-chart/) - Animated pie charts
- [Perfect Scrollbar 1.5.6](https://github.com/utatti/perfect-scrollbar) - Custom scrollbars
### JavaScript Libraries
- **[Chart.js 4.5.1](http://www.chartjs.org/)** - Modern charting library (replaces jQuery Sparkline)
- **[jsvectormap 1.7.0](https://github.com/themustafaomar/jsvectormap)** - Interactive vector maps (replaces jVectorMap)
- [Day.js 1.11.19](https://day.js.org/) - Modern 2KB date library (replaces Moment.js)
- [Masonry 4.2.2](https://masonry.desandro.com/) - Grid layouts
- **100% Vanilla JavaScript** - No jQuery or lodash dependencies
### Icons & Fonts
- [Font Awesome](http://fontawesome.io/) - Icon library
- [Themify Icons](https://themify.me/themify-icons) - Additional icons
- [Roboto Font](https://fonts.google.com/specimen/Roboto) - Google Fonts
### Additional Plugins
- **HTML5 Date Inputs** - Enhanced native date pickers (replaces Bootstrap Datepicker)
- [Skycons](https://darkskyapp.github.io/skycons/) - Animated weather icons
- [Load Google Maps API](https://github.com/yuanqing/load-google-maps-api) - Maps integration
## Changelog
See [CHANGELOG.md](CHANGELOG.md) for detailed version history.
📚 **[Online Documentation](https://puikinsh.github.io/Adminator-admin-dashboard/)** includes comprehensive guides for all features.
#### Latest Release: V 3.0.0 (2026-01-13)
- **New Utility Modules** - Events, Performance, Storage, Sanitize, Logger
- **Testing Infrastructure** - Vitest with coverage reporting
- **Code Splitting** - Separate chunks for Chart.js, FullCalendar, Bootstrap
- **Bundle Analyzer** - Visual bundle inspection via `npm run build:analyze`
- **Lodash Removed** - Custom Events utility replaces lodash (~70KB saved)
- **Documentation** - API reference and component development guide
- **TypeScript Declarations** - IDE support with type definitions
#### Previous Releases
- **V 2.9.0**: Comprehensive dependency updates, SCSS linting improvements
- **V 2.8.1**: Bootstrap 5.3.8, security updates, and enhanced tooling
- **V 2.8.0**: Webpack 5 asset modules and dependency modernization
- **V 2.7.1**: 100% jQuery-Free with modern vanilla JavaScript
- **V 2.6.0**: Complete Dark Mode System with theme switching
- **V 2.5.0**: Updated all dependencies, ESLint 9.x, zero vulnerabilities
- **V 2.1.0**: Upgraded all dependencies
- **V 2.0.0**: Upgrade to Bootstrap 5
- **V 1.1.0**: Upgrade to webpack 5
- **V 1.0.0**: Initial Release
## Authors
[Colorlib](https://colorlib.com)
## More Resources from Colorlib
- [Bootstrap Dashboards](https://colorlib.com/wp/free-bootstrap-admin-dashboard-templates/)
- [Bootstrap Templates](https://colorlib.com/wp/free-bootstrap-templates/)
- [HTML Templates](https://colorlib.com/wp/free-html-website-templates/)
- [Free Admin Dashboards](https://colorlib.com/wp/free-html5-admin-dashboard-templates/)
- [Website Templates](https://colorlib.com/wp/templates/)
- [Free CSS Templates](https://colorlib.com/wp/free-css-website-templates/)
- [WordPress Themes](https://colorlib.com/wp/free-wordpress-themes/)
## License
Adminator is licensed under The MIT License (MIT). Which means that you can use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the final products. But you always need to state that Colorlib is the original author of this template.
================================================
FILE: browserslist
================================================
# https://github.com/browserslist/browserslist#readme
>= 0.5%
last 2 major versions
not dead
Chrome >= 90
Firefox >= 90
Firefox ESR
iOS >= 15
Safari >= 15
not op_mini all
================================================
FILE: ci/getVersion.sh
================================================
echo $(sed 's/.*"version": "\(.*\)".*/\1/;t;d' ./package.json)
================================================
FILE: ci/verifyVersion.sh
================================================
VERSION=$1
TAG_EXISTS=$(git ls-remote --tags origin $VERSION | wc -l)
if [ $TAG_EXISTS -eq "1" ]; then
echo "The tag '$VERSION' already exists. Please update version in package.json.";
exit 1;
fi
echo "The tag '$VERSION' does not exist - success.";
================================================
FILE: docs/COMPONENT_GUIDE.md
================================================
# Component Development Guide
This guide explains how to create new components for the Adminator admin dashboard, following the established patterns and best practices.
## Table of Contents
- [Architecture Overview](#architecture-overview)
- [Creating a New Component](#creating-a-new-component)
- [Component Lifecycle](#component-lifecycle)
- [Theme Integration](#theme-integration)
- [Event Handling](#event-handling)
- [Mobile Considerations](#mobile-considerations)
- [Testing Components](#testing-components)
---
## Architecture Overview
Adminator uses a class-based component architecture with the following structure:
```
src/assets/scripts/
├── app.js # Main application controller
├── components/ # Reusable UI components
│ ├── Sidebar.js
│ └── Chart.js
├── utils/ # Utility modules
│ ├── dom.js # DOM manipulation
│ ├── theme.js # Theme management
│ ├── events.js # Event handling
│ ├── performance.js # Performance utilities
│ ├── logger.js # Development logging
│ └── date.js # Date utilities
└── [feature modules] # Feature-specific code
```
### Key Principles
1. **No jQuery** - Use vanilla JS and the `DOM` utility
2. **Event Delegation** - Use `Events.delegate()` for performance
3. **Theme Awareness** - Support light/dark modes via CSS variables
4. **Cleanup** - Always provide a `destroy()` method
5. **Mobile First** - Consider touch interactions
---
## Creating a New Component
### Step 1: Create the Component File
Create a new file in `src/assets/scripts/components/`:
```javascript
/**
* MyComponent - Description of what it does
*
* @module components/MyComponent
*/
import { DOM } from '../utils/dom';
import Events from '../utils/events';
import Logger from '../utils/logger';
class MyComponent {
/**
* Create a MyComponent instance
* @param {Object} [options={}] - Configuration options
* @param {string} [options.selector='.my-component'] - Root element selector
*/
constructor(options = {}) {
this.options = {
selector: '.my-component',
...options,
};
this.element = DOM.select(this.options.selector);
this.cleanupFunctions = [];
if (this.element) {
this.init();
}
}
/**
* Initialize the component
*/
init() {
Logger.debug('MyComponent initializing');
this.bindEvents();
this.render();
Logger.info('MyComponent initialized');
}
/**
* Bind event listeners
*/
bindEvents() {
// Use event delegation for child elements
const cleanup = Events.delegate(
this.element,
'click',
'.action-button',
(e, button) => this.handleAction(e, button)
);
this.cleanupFunctions.push(cleanup);
// Listen for theme changes
const themeCleanup = Events.on(window, 'adminator:themeChanged', () => {
this.onThemeChange();
});
this.cleanupFunctions.push(themeCleanup);
}
/**
* Handle action button clicks
* @param {Event} e - Click event
* @param {Element} button - Clicked button
*/
handleAction(e, button) {
e.preventDefault();
// Handle the action
}
/**
* Called when theme changes
*/
onThemeChange() {
// Update component for new theme
}
/**
* Render/update the component
*/
render() {
// Update DOM as needed
}
/**
* Destroy the component and clean up
*/
destroy() {
// Run all cleanup functions
this.cleanupFunctions.forEach(fn => fn());
this.cleanupFunctions = [];
Logger.debug('MyComponent destroyed');
}
}
export default MyComponent;
```
### Step 2: Register with the App
Add your component to `app.js`:
```javascript
import MyComponent from './components/MyComponent';
class AdminatorApp {
init() {
// ... existing init code ...
this.initMyComponent();
}
initMyComponent() {
if (DOM.exists('.my-component')) {
const myComponent = new MyComponent();
this.components.set('myComponent', myComponent);
}
}
}
```
### Step 3: Add Styles
Create styles in `src/assets/styles/spec/components/`:
```scss
// _my-component.scss
.my-component {
// Use CSS variables for theme support
background: var(--c-bkg-card);
color: var(--c-text-base);
border: 1px solid var(--c-border);
// Mobile-first responsive styles
padding: 1rem;
@media (min-width: 768px) {
padding: 1.5rem;
}
}
```
Import in `index.scss`:
```scss
@import 'components/my-component';
```
---
## Component Lifecycle
```
┌─────────────────┐
│ constructor │ - Store options
│ │ - Find root element
└────────┬────────┘
│
▼
┌─────────────────┐
│ init() │ - Bind events
│ │ - Initial render
└────────┬────────┘
│
▼
┌─────────────────┐
│ Active State │ - Handle events
│ │ - Respond to theme changes
└────────┬────────┘
│
▼
┌─────────────────┐
│ destroy() │ - Remove event listeners
│ │ - Clean up resources
└─────────────────┘
```
---
## Theme Integration
### Using CSS Variables
Always use CSS variables for colors:
```scss
.my-component {
// Good - supports theme switching
background: var(--c-bkg-card);
color: var(--c-text-base);
border-color: var(--c-border);
// Bad - hardcoded colors
// background: #ffffff;
// color: #212529;
}
```
### Available CSS Variables
| Variable | Description |
|----------|-------------|
| `--c-bkg-body` | Body background |
| `--c-bkg-card` | Card/panel background |
| `--c-text-base` | Primary text color |
| `--c-text-muted` | Secondary text color |
| `--c-border` | Border color |
| `--c-primary` | Primary accent color |
| `--c-success` | Success state color |
| `--c-danger` | Error/danger color |
| `--c-warning` | Warning color |
| `--c-info` | Info color |
### Responding to Theme Changes
```javascript
import Theme from '../utils/theme';
import Events from '../utils/events';
class MyComponent {
bindEvents() {
Events.on(window, 'adminator:themeChanged', (e) => {
const { theme } = e.detail; // 'light' or 'dark'
this.updateForTheme(theme);
});
}
updateForTheme(theme) {
// Get theme-specific colors
const colors = Theme.getChartColors();
// Update canvas, SVG, or other non-CSS elements
this.canvas.style.backgroundColor = colors.tooltipBg;
}
}
```
---
## Event Handling
### Event Delegation (Preferred)
Use event delegation for better performance:
```javascript
import Events from '../utils/events';
// Instead of adding to each button:
// buttons.forEach(btn => btn.addEventListener('click', ...))
// Use delegation (single listener):
Events.delegate(container, 'click', '.btn', (e, btn) => {
console.log('Button clicked:', btn.dataset.action);
});
```
### Cleanup Pattern
Always store cleanup functions and call them in `destroy()`:
```javascript
class MyComponent {
constructor() {
this.cleanupFunctions = [];
}
bindEvents() {
// Events.on returns a cleanup function
const cleanup1 = Events.on(this.element, 'click', this.handleClick);
this.cleanupFunctions.push(cleanup1);
const cleanup2 = Events.delegate(this.element, 'click', '.item', this.handleItem);
this.cleanupFunctions.push(cleanup2);
}
destroy() {
this.cleanupFunctions.forEach(fn => fn());
this.cleanupFunctions = [];
}
}
```
### Custom Events
Emit events for cross-component communication:
```javascript
import Events from '../utils/events';
// Emit an event
Events.emit(window, 'myComponent:action', {
type: 'update',
data: { id: 123 },
});
// Listen for events
Events.on(window, 'myComponent:action', (e) => {
console.log(e.detail.type, e.detail.data);
});
```
---
## Mobile Considerations
### Check for Mobile
```javascript
isMobile() {
return window.innerWidth <= 768;
}
```
### Touch-Friendly Interactions
- Minimum tap target: 44x44px
- Add hover states only on non-touch devices
- Consider swipe gestures for mobile
```scss
.my-button {
min-width: 44px;
min-height: 44px;
padding: 12px;
// Hover only on devices that support it
@media (hover: hover) {
&:hover {
background: var(--c-primary-hover);
}
}
// Active state for touch
&:active {
background: var(--c-primary-active);
}
}
```
### Responsive Events
Use `ResizeObserver` for responsive behavior:
```javascript
import Performance from '../utils/performance';
class MyComponent {
init() {
// React to element resize (more efficient than window.resize)
this.resizeCleanup = Performance.onResize(this.element, ({ width }) => {
this.updateLayout(width);
});
}
destroy() {
this.resizeCleanup?.();
}
}
```
---
## Testing Components
### Create a Test File
Create `tests/components/MyComponent.test.js`:
```javascript
import { describe, it, expect, beforeEach, vi } from 'vitest';
import MyComponent from '../../src/assets/scripts/components/MyComponent';
describe('MyComponent', () => {
beforeEach(() => {
document.body.innerHTML = `
Click
`;
});
it('initializes when element exists', () => {
const component = new MyComponent();
expect(component.element).not.toBeNull();
});
it('handles action button clicks', () => {
const component = new MyComponent();
const handler = vi.spyOn(component, 'handleAction');
document.querySelector('.action-button').click();
expect(handler).toHaveBeenCalled();
});
it('cleans up on destroy', () => {
const component = new MyComponent();
component.destroy();
expect(component.cleanupFunctions).toHaveLength(0);
});
});
```
### Run Tests
```bash
npm test # Watch mode
npm run test:run # Single run
npm run test:coverage # With coverage
```
---
## Example: Complete Component
Here's a complete example of a notification component:
```javascript
/**
* NotificationComponent - Toast notifications
*/
import { DOM } from '../utils/dom';
import Events from '../utils/events';
import Logger from '../utils/logger';
class NotificationComponent {
constructor(options = {}) {
this.options = {
container: '.notification-container',
duration: 5000,
position: 'top-right',
...options,
};
this.container = DOM.select(this.options.container);
this.notifications = new Map();
this.cleanupFunctions = [];
this.init();
}
init() {
// Create container if missing
if (!this.container) {
this.container = DOM.create('div', {
class: `notification-container notification-${this.options.position}`,
});
document.body.appendChild(this.container);
}
this.bindEvents();
Logger.info('NotificationComponent initialized');
}
bindEvents() {
// Delegate click events for close buttons
const cleanup = Events.delegate(
this.container,
'click',
'.notification-close',
(e, btn) => {
const notification = btn.closest('.notification');
if (notification) {
this.dismiss(notification.dataset.id);
}
}
);
this.cleanupFunctions.push(cleanup);
}
show(message, type = 'info') {
const id = `notification-${Date.now()}`;
const notification = DOM.create('div', {
class: `notification notification-${type}`,
'data-id': id,
}, [
DOM.create('span', { class: 'notification-message' }, [message]),
DOM.create('button', {
class: 'notification-close',
'aria-label': 'Close notification',
}, ['×']),
]);
this.container.appendChild(notification);
this.notifications.set(id, notification);
// Auto-dismiss
if (this.options.duration > 0) {
setTimeout(() => this.dismiss(id), this.options.duration);
}
// Animate in
requestAnimationFrame(() => {
notification.classList.add('notification-visible');
});
return id;
}
dismiss(id) {
const notification = this.notifications.get(id);
if (!notification) return;
notification.classList.remove('notification-visible');
notification.classList.add('notification-hiding');
setTimeout(() => {
notification.remove();
this.notifications.delete(id);
}, 300);
}
success(message) {
return this.show(message, 'success');
}
error(message) {
return this.show(message, 'error');
}
warning(message) {
return this.show(message, 'warning');
}
destroy() {
this.cleanupFunctions.forEach(fn => fn());
this.notifications.clear();
this.container?.remove();
Logger.debug('NotificationComponent destroyed');
}
}
export default NotificationComponent;
```
---
## Summary
When creating components:
1. **Follow the pattern** - Use the class structure with constructor, init, bindEvents, render, destroy
2. **Use utilities** - DOM, Events, Logger, Performance, Theme
3. **Support themes** - Use CSS variables, listen for theme changes
4. **Clean up** - Store and call cleanup functions in destroy()
5. **Test** - Write unit tests for your component
6. **Document** - Add JSDoc comments to all public methods
For questions or issues, see the main [README.md](../README.md) or open an issue on GitHub.
================================================
FILE: docs/README.md
================================================
# Adminator Documentation
This directory contains the complete documentation for Adminator Bootstrap 5 Admin Template.
## 📁 Documentation Structure
```
docs/
├── index.md # Homepage
├── _config.yml # GitHub Pages configuration
├── getting-started/ # Installation and setup guides
│ ├── installation.md
│ ├── project-structure.md
│ ├── development.md
│ └── build-deployment.md
├── components/ # Component documentation
│ ├── charts.md
│ ├── forms.md
│ ├── tables.md
│ ├── navigation.md
│ └── modals.md
├── customization/ # Theme and styling guides
│ ├── theme-system.md
│ ├── css-variables.md
│ ├── custom-themes.md
│ └── component-theming.md
├── api/ # API reference
│ ├── theme-api.md
│ ├── component-apis.md
│ ├── utilities.md
│ └── events.md
├── examples/ # Practical examples
│ ├── basic-setup.md
│ ├── custom-components.md
│ ├── theme-integration.md
│ └── advanced-patterns.md
├── deployment/ # Production deployment
│ ├── production-build.md
│ ├── static-hosting.md
│ ├── cdn-integration.md
│ └── performance.md
└── contributing/ # Contribution guidelines
├── development-setup.md
├── code-standards.md
├── pull-requests.md
└── issues.md
```
## 🚀 Hosting Strategy
### **GitHub Pages Setup**
1. **Main Branch Integration**: Documentation lives in the `docs/` folder of the main branch
2. **Automatic Deployment**: GitHub Pages automatically builds and deploys on every commit
3. **Custom Domain Support**: Can be configured with custom domains
4. **Jekyll Integration**: Uses Jekyll static site generator with the Minima theme
### **Benefits of This Approach**
✅ **Version Control**: Docs stay in sync with code releases
✅ **Free Hosting**: GitHub Pages provides free, reliable hosting
✅ **Easy Discovery**: Users find docs directly in the repository
✅ **SEO Friendly**: Searchable and indexable documentation
✅ **Collaboration**: Easy for contributors to update docs
✅ **Professional URLs**: Clean URLs like `username.github.io/repo/`
## 📖 Documentation Sections
### **🏁 Getting Started**
Complete setup and installation guides for new users.
### **🎨 Components**
Detailed documentation for all UI components and their usage.
### **🌙 Dark Mode & Theming**
Comprehensive guide to the theme system and customization options.
### **🔧 API Reference**
Complete API documentation for all JavaScript utilities and components.
### **💡 Examples**
Practical, copy-paste examples for common use cases.
### **🚀 Deployment**
Production deployment guides and performance optimization tips.
### **🤝 Contributing**
Guidelines for contributing to the project.
## 🔧 Local Development
To view the documentation locally:
```bash
# Install Jekyll (if not already installed)
gem install bundler jekyll
# Navigate to docs directory
cd docs
# Install dependencies
bundle install
# Serve locally
bundle exec jekyll serve
# Visit http://localhost:4000
```
## 📝 Writing Documentation
### **Markdown Guidelines**
- Use clear, descriptive headings
- Include code examples for all features
- Add cross-references between related sections
- Use emoji for visual appeal (sparingly)
- Include "Next Steps" sections to guide readers
### **Code Examples**
```markdown
\```javascript
// Always include working code examples
const example = Theme.current();
console.log(example);
\```
```
### **File Naming**
- Use lowercase with hyphens: `theme-system.md`
- Be descriptive but concise
- Group related files in subdirectories
## 🔗 Quick Links
- **[Live Documentation](https://puikinsh.github.io/Adminator-admin-dashboard/)** (Coming Soon)
- **[GitHub Repository](https://github.com/puikinsh/Adminator-admin-dashboard)**
- **[Live Demo](https://colorlib.com/polygon/adminator/index.html)**
## 📞 Support
For documentation issues:
- Open an issue with the `documentation` label
- Suggest improvements via pull requests
- Join discussions for larger documentation changes
---
**Happy documenting!** 📚
================================================
FILE: docs/_config.yml
================================================
# GitHub Pages Configuration for Adminator Documentation
# Site settings
title: "Adminator Documentation"
description: "Complete guide for the Adminator Bootstrap 5 Admin Dashboard Template with Dark Mode"
url: "https://puikinsh.github.io"
baseurl: "/Adminator-admin-dashboard"
# Build settings
markdown: kramdown
highlighter: rouge
remote_theme: just-the-docs/just-the-docs
# Plugin settings
plugins:
- jekyll-feed
- jekyll-sitemap
- jekyll-seo-tag
# Just the Docs theme configuration
color_scheme: nil
search_enabled: true
search:
heading_level: 2
previews: 2
preview_words_before: 3
preview_words_after: 3
tokenizer_separator: /[\s/]+/
rel_url: true
button: false
# Enable custom CSS
sass:
sass_dir: _sass
style: compressed
# Heading anchor links appear on hover over h1-h6 tags
heading_anchors: true
# Footer
footer_content: "Copyright © 2025 Colorlib. Distributed by an
MIT license. "
# Back to top link
back_to_top: true
back_to_top_text: "Back to top"
# Navigation Structure
nav_sort: case_insensitive
# Aux links for the upper right navigation
aux_links:
"GitHub Repository":
- "//github.com/puikinsh/Adminator-admin-dashboard"
"Live Demo":
- "//colorlib.com/polygon/adminator/index.html"
# Makes Aux links open in a new tab
aux_links_new_tab: true
# SEO
author: "Colorlib"
twitter:
username: colorlib
social:
name: "Colorlib"
links:
- https://github.com/puikinsh/Adminator-admin-dashboard
- https://colorlib.com
# Exclude from processing
exclude:
- Gemfile
- Gemfile.lock
- node_modules
- vendor/bundle/
- vendor/cache/
- vendor/gems/
- vendor/ruby/
# Code highlighting
kramdown:
syntax_highlighter: rouge
syntax_highlighter_opts:
css_class: 'highlight'
block:
line_numbers: false
start_line: 1
# Include
include:
- _pages
# Collections
collections:
pages:
output: true
permalink: /:name/
# Defaults
defaults:
- scope:
path: ""
type: "pages"
values:
layout: "page"
- scope:
path: ""
values:
layout: "default"
================================================
FILE: docs/_sass/custom.scss
================================================
// Custom styles for Adminator Documentation
// Fix code block formatting
.highlight {
.language-javascript,
.language-css,
.language-html,
.language-bash {
&:before {
content: attr(class);
display: block;
font-size: 0.75rem;
font-weight: 600;
color: #6b7280;
text-transform: uppercase;
letter-spacing: 0.05em;
margin-bottom: 0.5rem;
padding-bottom: 0.25rem;
border-bottom: 1px solid #e5e7eb;
}
}
}
// Better code block styling
pre.highlight {
background-color: #f8fafc !important;
border: 1px solid #e2e8f0;
border-radius: 6px;
font-size: 0.875rem;
line-height: 1.5;
overflow-x: auto;
code {
background: transparent !important;
border: none !important;
font-family: 'SFMono-Regular', 'Menlo', 'Monaco', 'Consolas', 'Liberation Mono', 'Courier New', monospace;
}
}
// Inline code styling
code:not(.highlighter-rouge) {
background-color: #f1f5f9 !important;
color: #475569 !important;
padding: 0.125rem 0.25rem !important;
border-radius: 3px !important;
font-size: 0.875em !important;
border: 1px solid #e2e8f0 !important;
}
// Feature cards styling
.feature-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 1.5rem;
margin: 2rem 0;
}
.feature-card {
padding: 1.5rem;
border: 1px solid #e5e7eb;
border-radius: 8px;
background: #ffffff;
h3 {
margin-top: 0;
color: #1f2937;
display: flex;
align-items: center;
gap: 0.5rem;
&:before {
content: "✨";
font-size: 1.25em;
}
}
}
// Button improvements
.btn {
&.btn-outline {
border: 1px solid #d1d5db;
color: #374151;
text-decoration: none;
&:hover {
background-color: #f9fafb;
border-color: #9ca3af;
}
}
&.btn-green {
background-color: #059669;
color: white;
border: 1px solid #059669;
&:hover {
background-color: #047857;
border-color: #047857;
}
}
}
// Table styling improvements
table {
border-collapse: collapse;
width: 100%;
margin: 1.5rem 0;
th, td {
padding: 0.75rem 1rem;
text-align: left;
border-bottom: 1px solid #e5e7eb;
}
th {
background-color: #f9fafb;
font-weight: 600;
color: #374151;
}
tr:hover {
background-color: #f9fafb;
}
}
// Navigation improvements
.site-nav {
.nav-list {
.nav-list-item {
&.active {
> .nav-list-link {
background-color: #eff6ff;
color: #1d4ed8;
}
}
}
}
}
// Custom alert boxes
.alert {
padding: 1rem 1.25rem;
margin: 1.5rem 0;
border-radius: 6px;
border-left: 4px solid;
&.alert-info {
background-color: #eff6ff;
border-left-color: #3b82f6;
color: #1e40af;
}
&.alert-warning {
background-color: #fffbeb;
border-left-color: #f59e0b;
color: #92400e;
}
&.alert-success {
background-color: #f0fdf4;
border-left-color: #10b981;
color: #065f46;
}
}
// Dark mode improvements for code
@media (prefers-color-scheme: dark) {
pre.highlight {
background-color: #1f2937 !important;
border-color: #374151;
code {
color: #e5e7eb !important;
}
}
code:not(.highlighter-rouge) {
background-color: #374151 !important;
color: #d1d5db !important;
border-color: #4b5563 !important;
}
}
================================================
FILE: docs/api/theme-api.md
================================================
---
layout: default
title: Theme API
nav_order: 1
parent: API Reference
---
# Theme API Reference
{: .no_toc }
## Table of contents
{: .no_toc .text-delta }
1. TOC
{:toc}
Complete API documentation for the Adminator Theme utility system.
## Overview
The Theme API provides programmatic control over Adminator's dark mode and theming system. It includes methods for theme management, color retrieval, and event handling.
## Theme Object
The global `Theme` object is available throughout the application:
```javascript
// Available globally
window.Theme
// or simply
Theme
```
## Core Methods
### `Theme.apply(theme)`
Applies a specific theme to the application.
**Parameters:**
- `theme` (string): The theme to apply (`'light'` or `'dark'`)
**Returns:** `void`
**Example:**
```javascript
// Apply dark theme
Theme.apply('dark');
// Apply light theme
Theme.apply('light');
```
**Side Effects:**
- Sets `data-theme` attribute on `document.documentElement`
- Updates Chart.js global defaults (if Chart.js is loaded)
- Saves theme preference to localStorage
- Dispatches `adminator:themeChanged` event
---
### `Theme.toggle()`
Toggles between light and dark themes.
**Parameters:** None
**Returns:** `void`
**Example:**
```javascript
// Switch from current theme to opposite
Theme.toggle();
// If current theme is 'light', switches to 'dark'
// If current theme is 'dark', switches to 'light'
```
---
### `Theme.current()`
Gets the currently active theme.
**Parameters:** None
**Returns:** `string` - The current theme (`'light'` or `'dark'`)
**Example:**
```javascript
const currentTheme = Theme.current();
console.log(currentTheme); // 'light' or 'dark'
// Use in conditional logic
if (Theme.current() === 'dark') {
// Dark theme specific logic
}
```
---
### `Theme.init()`
Initializes the theme system. Called automatically on page load.
**Parameters:** None
**Returns:** `void`
**Example:**
```javascript
// Manual initialization (usually not needed)
Theme.init();
```
**Behavior:**
- Checks for stored theme preference in localStorage
- If no stored preference, detects OS color scheme preference
- Applies the determined theme
- Sets up Chart.js defaults if available
---
### `Theme.getCSSVar(varName)`
Retrieves the computed value of a CSS custom property.
**Parameters:**
- `varName` (string): The CSS variable name (including `--` prefix)
**Returns:** `string` - The computed CSS variable value
**Example:**
```javascript
// Get primary color
const primaryColor = Theme.getCSSVar('--c-primary');
console.log(primaryColor); // '#4b7cf3'
// Get background color
const bgColor = Theme.getCSSVar('--c-bkg-body');
console.log(bgColor); // '#f8f9fa' or '#181a1f'
```
## Specialized Color Methods
### `Theme.getChartColors()`
Gets color values optimized for Chart.js components.
**Parameters:** None
**Returns:** `object` - Chart color configuration
**Return Object:**
```javascript
{
textColor: string, // Primary text color
mutedColor: string, // Secondary text color
borderColor: string, // Border colors
gridColor: string, // Grid line colors
tooltipBg: string // Tooltip background
}
```
**Example:**
```javascript
const chartColors = Theme.getChartColors();
const chart = new Chart(ctx, {
options: {
plugins: {
legend: {
labels: {
color: chartColors.textColor
}
}
},
scales: {
y: {
ticks: { color: chartColors.mutedColor },
grid: { color: chartColors.gridColor }
}
}
}
});
```
---
### `Theme.getVectorMapColors()`
Gets color palette for vector map components.
**Parameters:** None
**Returns:** `object` - Vector map color configuration
**Return Object:**
```javascript
{
backgroundColor: string, // Map background
borderColor: string, // Map borders
regionColor: string, // Default region color
markerFill: string, // Marker fill color
markerStroke: string, // Marker stroke color
hoverColor: string, // Hover state color
selectedColor: string, // Selected state color
scaleStart: string, // Color scale start
scaleEnd: string, // Color scale end
scaleLight: string, // Light scale color
scaleDark: string // Dark scale color
}
```
**Example:**
```javascript
const mapColors = Theme.getVectorMapColors();
$('#world-map').vectorMap({
backgroundColor: mapColors.backgroundColor,
regionStyle: {
initial: {
fill: mapColors.regionColor,
stroke: mapColors.borderColor
},
hover: {
fill: mapColors.hoverColor
}
}
});
```
---
### `Theme.getSparklineColors()`
Gets color palette for Sparkline chart components.
**Parameters:** None
**Returns:** `object` - Sparkline color configuration
**Return Object:**
```javascript
{
success: string, // Success state color
purple: string, // Purple variant
info: string, // Info state color
danger: string, // Danger state color
light: string // Light variant
}
```
**Example:**
```javascript
const sparklineColors = Theme.getSparklineColors();
$('.sparkline-success').sparkline(data, {
lineColor: sparklineColors.success,
fillColor: false
});
```
## Event System
### Theme Change Event
The theme system dispatches custom events when themes change.
**Event Name:** `adminator:themeChanged`
**Event Detail:**
```javascript
{
theme: string // The new theme ('light' or 'dark')
}
```
**Example:**
```javascript
// Listen for theme changes
window.addEventListener('adminator:themeChanged', (event) => {
const newTheme = event.detail.theme;
console.log('Theme changed to:', newTheme);
// Update custom components
updateCustomComponent(newTheme);
});
// Alternative using jQuery
$(window).on('adminator:themeChanged', function(event) {
const newTheme = event.originalEvent.detail.theme;
// Handle theme change
});
```
## Storage Management
### Local Storage Key
The theme preference is stored using the key: `'adminator-theme'`
**Example:**
```javascript
// Manually check stored theme
const storedTheme = localStorage.getItem('adminator-theme');
console.log(storedTheme); // 'light', 'dark', or null
// Manually set theme preference
localStorage.setItem('adminator-theme', 'dark');
```
### Fallback Handling
The Theme API gracefully handles storage unavailability:
```javascript
// Internal implementation example
try {
localStorage.setItem('adminator-theme', theme);
} catch (_) {
// Storage not available - theme won't persist
// But theme will still work for current session
}
```
## Integration Examples
### React Integration
```javascript
import { useEffect, useState } from 'react';
function useTheme() {
const [theme, setTheme] = useState(Theme.current());
useEffect(() => {
const handleThemeChange = (event) => {
setTheme(event.detail.theme);
};
window.addEventListener('adminator:themeChanged', handleThemeChange);
return () => {
window.removeEventListener('adminator:themeChanged', handleThemeChange);
};
}, []);
return {
theme,
toggle: Theme.toggle,
setTheme: Theme.apply
};
}
```
### Vue.js Integration
```javascript
export default {
data() {
return {
currentTheme: Theme.current()
}
},
mounted() {
window.addEventListener('adminator:themeChanged', this.handleThemeChange);
},
beforeDestroy() {
window.removeEventListener('adminator:themeChanged', this.handleThemeChange);
},
methods: {
handleThemeChange(event) {
this.currentTheme = event.detail.theme;
},
toggleTheme() {
Theme.toggle();
}
}
}
```
### Custom Component Integration
```javascript
class CustomWidget {
constructor(element) {
this.element = element;
this.currentTheme = Theme.current();
this.init();
this.bindEvents();
}
init() {
this.updateTheme(this.currentTheme);
}
bindEvents() {
window.addEventListener('adminator:themeChanged', (event) => {
this.currentTheme = event.detail.theme;
this.updateTheme(this.currentTheme);
});
}
updateTheme(theme) {
// Update widget based on theme
if (theme === 'dark') {
this.element.classList.add('widget-dark');
this.element.style.backgroundColor = Theme.getCSSVar('--c-bkg-card');
} else {
this.element.classList.remove('widget-dark');
this.element.style.backgroundColor = Theme.getCSSVar('--c-bkg-card');
}
}
}
```
## Error Handling
### Browser Compatibility
```javascript
// Check for CSS custom property support
function supportsCSSVariables() {
return window.CSS && CSS.supports('color', 'var(--fake-var)');
}
if (!supportsCSSVariables()) {
console.warn('CSS variables not supported - theme switching limited');
}
```
### LocalStorage Availability
```javascript
// Check for localStorage support
function supportsLocalStorage() {
try {
return 'localStorage' in window && window['localStorage'] !== null;
} catch (e) {
return false;
}
}
if (!supportsLocalStorage()) {
console.warn('localStorage not available - theme preference won\'t persist');
}
```
## Performance Considerations
### Debounced Theme Changes
For applications with many theme-aware components:
```javascript
let themeChangeTimeout;
window.addEventListener('adminator:themeChanged', (event) => {
clearTimeout(themeChangeTimeout);
themeChangeTimeout = setTimeout(() => {
// Expensive theme update operations
updateManyComponents(event.detail.theme);
}, 100);
});
```
### Efficient CSS Variable Reading
Cache CSS variable values when possible:
```javascript
class ThemeCache {
constructor() {
this.cache = new Map();
this.cacheTheme = null;
}
getCSSVar(varName) {
const currentTheme = Theme.current();
if (this.cacheTheme !== currentTheme) {
this.cache.clear();
this.cacheTheme = currentTheme;
}
if (!this.cache.has(varName)) {
const value = Theme.getCSSVar(varName);
this.cache.set(varName, value);
}
return this.cache.get(varName);
}
}
```
---
**Next:** Explore [Component APIs](component-apis.md) or check out [Theme Integration Examples](../examples/theme-integration.md).
================================================
FILE: docs/api.md
================================================
# Adminator API Reference
Complete API documentation for Adminator utilities and components.
## Table of Contents
- [DOM Utilities](#dom-utilities)
- [Theme Manager](#theme-manager)
- [Events Utilities](#events-utilities)
- [Performance Utilities](#performance-utilities)
- [Logger](#logger)
- [Date Utilities](#date-utilities)
- [Custom Events](#custom-events)
---
## DOM Utilities
jQuery-like DOM manipulation using vanilla JavaScript.
```javascript
import { DOM } from './utils/dom';
```
### Selection
#### `DOM.select(selector, context?)`
Select a single element.
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| selector | string | - | CSS selector |
| context | Element | document | Context to search within |
**Returns:** `Element | null`
```javascript
const header = DOM.select('.header');
const navItem = DOM.select('.nav-item', sidebar);
```
#### `DOM.selectAll(selector, context?)`
Select all matching elements.
**Returns:** `Element[]`
```javascript
const buttons = DOM.selectAll('.btn');
buttons.forEach(btn => console.log(btn));
```
#### `DOM.exists(selector)`
Check if element exists.
**Returns:** `boolean`
```javascript
if (DOM.exists('.sidebar')) {
initSidebar();
}
```
### Events
#### `DOM.on(element, event, handler, options?)`
Add event listener.
```javascript
DOM.on('.btn', 'click', handleClick);
DOM.on(button, 'click', handleClick, { once: true });
```
#### `DOM.off(element, event, handler)`
Remove event listener.
```javascript
DOM.off(button, 'click', handleClick);
```
### Classes
#### `DOM.addClass(element, className)`
```javascript
DOM.addClass('.menu', 'open');
```
#### `DOM.removeClass(element, className)`
```javascript
DOM.removeClass('.menu', 'open');
```
#### `DOM.toggleClass(element, className)`
```javascript
DOM.toggleClass('.dropdown', 'show');
```
#### `DOM.hasClass(element, className)`
**Returns:** `boolean`
```javascript
if (DOM.hasClass('.menu', 'open')) {
closeMenu();
}
```
### Attributes
#### `DOM.attr(element, name, value?)`
Get or set attribute.
```javascript
// Get
const href = DOM.attr(link, 'href');
// Set
DOM.attr(link, 'href', '/new-page');
```
#### `DOM.data(element, name, value?)`
Get or set data attribute.
```javascript
// Get data-id
const id = DOM.data(row, 'id');
// Set data-id
DOM.data(row, 'id', '123');
```
### Animations
#### `DOM.slideUp(element, duration?)`
**Returns:** `Promise
`
```javascript
await DOM.slideUp('.panel', 300);
```
#### `DOM.slideDown(element, duration?)`
**Returns:** `Promise`
```javascript
await DOM.slideDown('.panel', 300);
```
#### `DOM.fadeIn(element, duration?)`
**Returns:** `Promise`
```javascript
await DOM.fadeIn('.modal', 200);
```
#### `DOM.fadeOut(element, duration?)`
**Returns:** `Promise`
```javascript
await DOM.fadeOut('.modal', 200);
```
### Utilities
#### `DOM.dimensions(element)`
Get element dimensions.
**Returns:** `{ width, height, top, left, bottom, right } | null`
```javascript
const { width, height } = DOM.dimensions('.card');
```
#### `DOM.ready(callback)`
Execute when DOM is ready.
```javascript
DOM.ready(() => {
initApp();
});
```
#### `DOM.create(tag, attrs?, children?)`
Create an element.
```javascript
const button = DOM.create('button', {
class: 'btn btn-primary',
type: 'submit',
}, ['Submit']);
```
---
## Theme Manager
Light/dark mode switching with persistence.
```javascript
import Theme from './utils/theme';
```
### Methods
#### `Theme.init()`
Initialize theme system. Detects OS preference on first visit.
**Returns:** `'light' | 'dark'`
#### `Theme.apply(theme)`
Apply a theme ('light' or 'dark').
**Returns:** `boolean`
```javascript
Theme.apply('dark');
```
#### `Theme.toggle()`
Toggle between light and dark.
**Returns:** `'light' | 'dark'` - The new theme
```javascript
const newTheme = Theme.toggle();
```
#### `Theme.current()`
Get current theme.
**Returns:** `'light' | 'dark'`
#### `Theme.isDark()` / `Theme.isLight()`
Check current theme state.
**Returns:** `boolean`
#### `Theme.getCSSVar(varName)`
Get a CSS variable value.
```javascript
const bgColor = Theme.getCSSVar('--c-bkg-body');
```
#### `Theme.getChartColors()`
Get theme-aware Chart.js colors.
**Returns:** `{ textColor, mutedColor, borderColor, gridColor, tooltipBg }`
---
## Events Utilities
Efficient event handling with delegation and cleanup.
```javascript
import Events from './utils/events';
```
### Methods
#### `Events.on(element, event, handler, options?)`
Add event listener with cleanup support.
**Returns:** `Function` - Cleanup function
```javascript
const cleanup = Events.on(button, 'click', handleClick);
// Later: cleanup();
```
#### `Events.delegate(parent, event, selector, handler, options?)`
Event delegation - single listener for many elements.
**Returns:** `Function` - Cleanup function
```javascript
Events.delegate(document, 'click', '.btn', (e, btn) => {
console.log('Clicked:', btn);
});
```
#### `Events.once(element, event, handler)`
One-time event listener.
#### `Events.debounce(handler, delay?)`
Create debounced handler (default: 250ms).
```javascript
const debouncedSearch = Events.debounce(search, 300);
```
#### `Events.throttle(handler, limit?)`
Create throttled handler (default: 250ms).
```javascript
const throttledScroll = Events.throttle(onScroll, 100);
```
#### `Events.emit(target, eventName, detail?, options?)`
Dispatch a custom event.
```javascript
Events.emit(window, 'myapp:action', { type: 'save' });
```
---
## Performance Utilities
ResizeObserver, IntersectionObserver, and optimization utilities.
```javascript
import Performance from './utils/performance';
```
### Methods
#### `Performance.onResize(element, callback)`
Observe element resize.
**Returns:** `Function` - Cleanup function
```javascript
const unobserve = Performance.onResize(chart, ({ width, height }) => {
chart.resize(width, height);
});
```
#### `Performance.onVisible(element, callback, options?)`
Observe when element enters viewport.
```javascript
Performance.onVisible(element, ({ isIntersecting }) => {
if (isIntersecting) loadContent();
});
```
#### `Performance.lazyLoad(element, loadFn, options?)`
Lazy load when element becomes visible.
```javascript
Performance.lazyLoad(chartContainer, () => initChart());
```
#### `Performance.batch(readFn, writeFn)`
Batch DOM reads and writes.
#### `Performance.nextFrame(callback)`
Execute on next animation frame.
#### `Performance.whenIdle(callback, options?)`
Execute when browser is idle.
#### `Performance.preload(url, as?)`
Preload a resource.
```javascript
await Performance.preload('/images/hero.jpg', 'image');
```
#### `Performance.cleanup()`
Cleanup all observers.
---
## Logger
Development-only logging utility.
```javascript
import Logger from './utils/logger';
```
All methods are no-ops in production.
### Methods
```javascript
Logger.info('Message', { context });
Logger.warn('Warning', { context });
Logger.error('Error', error);
Logger.debug('Debug info', { data });
Logger.group('Group Name');
Logger.groupEnd();
Logger.time('operation');
Logger.timeEnd('operation');
Logger.table(arrayOfObjects);
```
---
## Date Utilities
Date handling using Day.js.
```javascript
import DateUtils from './utils/date';
```
### Methods
```javascript
DateUtils.now() // Current date
DateUtils.format.short(date) // "Jan 15, 2024"
DateUtils.format.long(date) // "January 15, 2024"
DateUtils.format.time(date) // "2:30 PM"
DateUtils.format.relative(date) // "2 hours ago"
DateUtils.form.toInputValue(date) // HTML date input format
DateUtils.form.fromInputValue(str) // Parse HTML date input
DateUtils.compare.isBefore(d1, d2)
DateUtils.compare.isAfter(d1, d2)
DateUtils.compare.isSame(d1, d2)
```
---
## Custom Events
### `adminator:ready`
Fired when app is fully initialized.
```javascript
window.addEventListener('adminator:ready', (e) => {
const { app } = e.detail;
});
```
### `adminator:themeChanged`
Fired when theme changes.
```javascript
window.addEventListener('adminator:themeChanged', (e) => {
const { theme } = e.detail; // 'light' or 'dark'
});
```
---
## TypeScript Support
TypeScript declarations are available in `types/adminator.d.ts`.
```javascript
import Theme from './utils/theme';
Theme.apply('dark'); // Autocomplete works
```
---
## Import Patterns
```javascript
// Individual (recommended)
import { DOM } from './utils/dom';
import Theme from './utils/theme';
// Barrel import
import { DOM, Theme, Events, Performance, Logger } from './utils';
```
================================================
FILE: docs/customization/theme-system.md
================================================
---
layout: default
title: Theme System
nav_order: 1
parent: Customization
---
# Theme System Overview
{: .no_toc }
## Table of contents
{: .no_toc .text-delta }
1. TOC
{:toc}
Adminator v2.6.0 introduces a comprehensive dark mode system with intelligent theme switching and component integration.
## 🌟 Features
### Core Capabilities
- **🌗 Smart Toggle**: Bootstrap-based switch with sun/moon icons
- **🔄 OS Detection**: Automatically detects and applies system preference
- **💾 Persistent Storage**: Remembers theme choice across browser sessions
- **⚡ Instant Switching**: Real-time theme updates without page reload
- **🎯 Component Integration**: All elements are theme-aware
### Visual Enhancements
- **🎨 Semantic Colors**: Consistent color variables across components
- **📊 Chart Integration**: Dynamic color schemes for Chart.js
- **🗓️ Calendar Support**: Dark-mode aware FullCalendar
- **🗺️ Map Theming**: Custom color palettes for vector and Google maps
## 🚀 Quick Start
### Basic Usage
The theme system is automatically initialized when the page loads:
```javascript
// The theme toggle is automatically injected into the header
// Users can click the Light/Dark switch to change themes
```
### Programmatic Control
```javascript
// Get current theme
const currentTheme = Theme.current(); // 'light' or 'dark'
// Switch themes
Theme.toggle();
// Set specific theme
Theme.apply('dark');
Theme.apply('light');
// Listen for theme changes
window.addEventListener('adminator:themeChanged', (event) => {
console.log('Theme changed to:', event.detail.theme);
// Your custom logic here
});
```
## 🎨 CSS Variables
### Core Theme Variables
```css
:root {
/* Backgrounds */
--c-bkg-body: #f8f9fa; /* Main page background */
--c-bkg-card: #ffffff; /* Card and panel backgrounds */
--c-bkg-sidebar: #ffffff; /* Sidebar background */
/* Text Colors */
--c-text-base: #212529; /* Primary text color */
--c-text-muted: #6c757d; /* Secondary text color */
/* UI Elements */
--c-border: #e2e5e8; /* Border colors */
--c-primary: #4b7cf3; /* Primary brand color */
--c-success: #2ecc71; /* Success state color */
--c-danger: #e74c3c; /* Error state color */
}
/* Dark theme overrides */
[data-theme="dark"] {
--c-bkg-body: #181a1f;
--c-bkg-card: #20232a;
--c-bkg-sidebar: #20232a;
--c-text-base: #e8eaed;
--c-text-muted: #9ca3af;
--c-border: #2b2f36;
/* ... additional dark theme variables */
}
```
### Component-Specific Variables
```css
:root {
/* Vector Maps */
--vmap-bg-color: #ffffff;
--vmap-region-color: #e4ecef;
--vmap-hover-color: #ffffff;
/* Charts */
--sparkline-success: #4caf50;
--sparkline-info: #03a9f3;
--sparkline-danger: #f96262;
/* Maps */
--gmap-landscape-hue: #FFBB00;
--gmap-water-hue: #0078FF;
}
```
## 🔧 Implementation Details
### Theme Toggle Component
The theme toggle is automatically injected into the navigation:
```html
Light
Dark
```
### Theme Detection Logic
```javascript
// Automatic OS preference detection
if (!localStorage.getItem('adminator-theme')) {
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
Theme.apply(prefersDark ? 'dark' : 'light');
} else {
Theme.apply(Theme.current());
}
```
## 🎯 Component Integration
### Chart.js Integration
Charts automatically update colors when themes change:
```javascript
// Chart colors are automatically updated
const chartColors = Theme.getChartColors();
// Returns: { textColor, mutedColor, borderColor, gridColor, tooltipBg }
// Custom chart configuration
const chart = new Chart(ctx, {
data: data,
options: {
plugins: {
legend: {
labels: {
color: chartColors.textColor
}
}
},
scales: {
y: {
ticks: {
color: chartColors.mutedColor
},
grid: {
color: chartColors.gridColor
}
}
}
}
});
```
### FullCalendar Integration
Calendar styling updates automatically:
```css
/* Automatic dark mode calendar styling */
.fc {
background: var(--c-bkg-card);
color: var(--c-text-base);
}
.fc-event {
border-color: var(--c-border) !important;
}
.fc-daygrid-day {
border-color: var(--c-border) !important;
}
```
### Vector Maps Integration
Maps use theme-aware color palettes:
```javascript
// Get vector map colors for current theme
const mapColors = Theme.getVectorMapColors();
/* Returns:
{
backgroundColor: '#ffffff' | '#20232a',
regionColor: '#e4ecef' | '#2b2f36',
hoverColor: '#ffffff' | '#181a1f',
// ... other map-specific colors
}
*/
```
## 🎨 Custom Styling
### Using CSS Variables
Style your components with theme-aware variables:
```css
.my-custom-component {
background: var(--c-bkg-card);
color: var(--c-text-base);
border: 1px solid var(--c-border);
border-radius: 8px;
padding: 1rem;
}
.my-custom-button {
background: var(--c-primary);
color: white;
border: none;
padding: 0.5rem 1rem;
border-radius: 4px;
}
.my-custom-button:hover {
background: var(--c-primary);
opacity: 0.9;
}
```
### Creating Theme-Aware Components
```javascript
class MyCustomComponent {
constructor(element) {
this.element = element;
this.init();
// Listen for theme changes
window.addEventListener('adminator:themeChanged', (event) => {
this.updateTheme(event.detail.theme);
});
}
updateTheme(theme) {
// Update component based on theme
if (theme === 'dark') {
this.element.classList.add('dark-mode');
} else {
this.element.classList.remove('dark-mode');
}
}
}
```
## 🔍 Advanced Usage
### Custom Theme Colors
Override default theme colors:
```css
:root {
/* Custom brand colors */
--c-primary: #your-brand-color;
--c-success: #your-success-color;
}
[data-theme="dark"] {
/* Custom dark theme colors */
--c-primary: #your-dark-brand-color;
--c-success: #your-dark-success-color;
}
```
### Theme-Specific Images
Use different images for light/dark themes:
```css
.logo {
background-image: url('logo-light.png');
}
[data-theme="dark"] .logo {
background-image: url('logo-dark.png');
}
```
### Dynamic Theme Updates
Create dynamic theme switching animations:
```css
/* Smooth theme transitions */
* {
transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease;
}
/* Disable transitions during theme switch */
.theme-transitioning * {
transition: none !important;
}
```
## 🐛 Troubleshooting
### Common Issues
#### Theme Not Persisting
```javascript
// Check localStorage availability
if (typeof(Storage) !== "undefined") {
// localStorage is available
} else {
// No web storage support
console.warn('localStorage not available - theme won\'t persist');
}
```
#### Components Not Updating
```javascript
// Ensure components listen for theme changes
window.addEventListener('adminator:themeChanged', (event) => {
// Force component update
myComponent.refresh();
});
```
#### CSS Variables Not Working
```css
/* Fallback for older browsers */
.my-component {
background: #ffffff; /* Fallback */
background: var(--c-bkg-card); /* Modern */
}
```
## 📚 Related Documentation
- **[CSS Variables Reference](css-variables.md)** - Complete variable list
- **[Custom Theme Creation](custom-themes.md)** - Create your own themes
- **[Component Theming](component-theming.md)** - Theme individual components
---
**Ready to customize?** Start with the [CSS Variables Reference](css-variables.md) or explore [Custom Theme Creation](custom-themes.md)!
================================================
FILE: docs/customization.md
================================================
---
layout: default
title: Customization
nav_order: 3
has_children: true
---
# Customization
Learn how to customize Adminator's appearance and behavior.
This section covers the dark mode system, theming, CSS variables, and custom component creation.
================================================
FILE: docs/examples/theme-integration.md
================================================
---
layout: default
title: Theme Integration
nav_order: 1
parent: Examples
---
# Theme Integration Examples
{: .no_toc }
## Table of contents
{: .no_toc .text-delta }
1. TOC
{:toc}
Practical examples for integrating custom components with Adminator's theme system.
## Basic Theme-Aware Component
### Simple CSS Integration
Create components that automatically adapt to theme changes:
```html
```
```css
/* CSS using theme variables */
.custom-widget {
background: var(--c-bkg-card);
color: var(--c-text-base);
border: 1px solid var(--c-border);
border-radius: 8px;
padding: 1.5rem;
margin-bottom: 1rem;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.widget-title {
color: var(--c-text-base);
margin-bottom: 1rem;
font-size: 1.25rem;
font-weight: 600;
}
.widget-content {
color: var(--c-text-muted);
line-height: 1.6;
}
.widget-button {
background: var(--c-primary);
color: white;
border: none;
padding: 0.5rem 1rem;
border-radius: 4px;
cursor: pointer;
transition: opacity 0.2s ease;
}
.widget-button:hover {
opacity: 0.9;
}
/* Dark theme specific adjustments */
[data-theme="dark"] .custom-widget {
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
}
```
## JavaScript Integration
### Theme-Aware Class Component
```javascript
class ThemeAwareWidget {
constructor(element, options = {}) {
this.element = element;
this.options = {
autoUpdate: true,
customColors: {},
...options
};
this.currentTheme = Theme.current();
this.init();
}
init() {
this.render();
this.bindEvents();
this.updateTheme(this.currentTheme);
}
render() {
this.element.innerHTML = `
`;
}
bindEvents() {
if (this.options.autoUpdate) {
window.addEventListener('adminator:themeChanged', (event) => {
this.currentTheme = event.detail.theme;
this.updateTheme(this.currentTheme);
});
}
}
updateTheme(theme) {
const widget = this.element.querySelector('.theme-widget');
// Update theme indicator
const indicator = widget.querySelector('.theme-indicator');
indicator.textContent = theme;
indicator.className = `theme-indicator theme-${theme}`;
// Update color samples
const samples = widget.querySelectorAll('.color-sample');
samples.forEach(sample => {
const colorType = sample.dataset.color;
const cssVar = `--c-${colorType}`;
const color = Theme.getCSSVar(cssVar);
sample.style.backgroundColor = color;
});
// Apply custom colors if provided
if (this.options.customColors[theme]) {
Object.entries(this.options.customColors[theme]).forEach(([property, value]) => {
widget.style.setProperty(property, value);
});
}
// Trigger custom update callback
if (this.options.onThemeChange) {
this.options.onThemeChange(theme, this);
}
}
// Public methods
setTheme(theme) {
Theme.apply(theme);
}
getCurrentColors() {
return {
primary: Theme.getCSSVar('--c-primary'),
success: Theme.getCSSVar('--c-success'),
danger: Theme.getCSSVar('--c-danger'),
background: Theme.getCSSVar('--c-bkg-card'),
text: Theme.getCSSVar('--c-text-base')
};
}
destroy() {
// Cleanup event listeners
window.removeEventListener('adminator:themeChanged', this.updateTheme);
}
}
// Usage
const widget = new ThemeAwareWidget(document.getElementById('my-widget'), {
customColors: {
dark: {
'--custom-accent': '#ff6b6b'
},
light: {
'--custom-accent': '#4dabf7'
}
},
onThemeChange: (theme, instance) => {
console.log(`Widget theme changed to: ${theme}`);
}
});
```
## Chart Integration
### Theme-Aware Chart.js
```javascript
class ThemeChart {
constructor(canvas, data, options = {}) {
this.canvas = canvas;
this.data = data;
this.options = options;
this.chart = null;
this.init();
}
init() {
this.createChart();
this.bindThemeEvents();
}
createChart() {
const colors = Theme.getChartColors();
const config = {
type: this.options.type || 'line',
data: this.processData(this.data, colors),
options: {
responsive: true,
plugins: {
legend: {
labels: {
color: colors.textColor,
usePointStyle: true
}
},
tooltip: {
backgroundColor: colors.tooltipBg,
titleColor: colors.textColor,
bodyColor: colors.textColor,
borderColor: colors.borderColor,
borderWidth: 1
}
},
scales: {
x: {
ticks: {
color: colors.mutedColor
},
grid: {
color: colors.gridColor
}
},
y: {
ticks: {
color: colors.mutedColor
},
grid: {
color: colors.gridColor
}
}
},
...this.options.chartOptions
}
};
this.chart = new Chart(this.canvas, config);
}
processData(data, colors) {
return {
...data,
datasets: data.datasets.map((dataset, index) => ({
...dataset,
borderColor: dataset.borderColor || this.getDatasetColor(index, colors),
backgroundColor: dataset.backgroundColor || this.getDatasetColor(index, colors, 0.2)
}))
};
}
getDatasetColor(index, colors, alpha = 1) {
const colorKeys = ['primary', 'success', 'danger', 'info', 'warning'];
const colorKey = colorKeys[index % colorKeys.length];
const color = Theme.getCSSVar(`--c-${colorKey}`);
if (alpha < 1) {
// Convert hex to rgba
const r = parseInt(color.slice(1, 3), 16);
const g = parseInt(color.slice(3, 5), 16);
const b = parseInt(color.slice(5, 7), 16);
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
}
return color;
}
bindThemeEvents() {
window.addEventListener('adminator:themeChanged', () => {
this.updateChart();
});
}
updateChart() {
if (!this.chart) return;
const colors = Theme.getChartColors();
// Update chart options
this.chart.options.plugins.legend.labels.color = colors.textColor;
this.chart.options.plugins.tooltip.backgroundColor = colors.tooltipBg;
this.chart.options.plugins.tooltip.titleColor = colors.textColor;
this.chart.options.plugins.tooltip.bodyColor = colors.textColor;
this.chart.options.plugins.tooltip.borderColor = colors.borderColor;
// Update scales
this.chart.options.scales.x.ticks.color = colors.mutedColor;
this.chart.options.scales.x.grid.color = colors.gridColor;
this.chart.options.scales.y.ticks.color = colors.mutedColor;
this.chart.options.scales.y.grid.color = colors.gridColor;
// Update dataset colors
this.chart.data.datasets.forEach((dataset, index) => {
dataset.borderColor = this.getDatasetColor(index, colors);
dataset.backgroundColor = this.getDatasetColor(index, colors, 0.2);
});
this.chart.update();
}
destroy() {
if (this.chart) {
this.chart.destroy();
}
}
}
// Usage
const chartData = {
labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'],
datasets: [{
label: 'Sales',
data: [12, 19, 3, 5, 2, 3]
}, {
label: 'Revenue',
data: [2, 3, 20, 5, 1, 4]
}]
};
const themeChart = new ThemeChart(
document.getElementById('myChart'),
chartData,
{
type: 'line',
chartOptions: {
tension: 0.4
}
}
);
```
## Custom Modal Integration
### Theme-Aware Modal
```javascript
class ThemeModal {
constructor(options = {}) {
this.options = {
title: 'Modal Title',
content: '',
size: 'md', // sm, md, lg, xl
backdrop: true,
keyboard: true,
...options
};
this.modal = null;
this.element = null;
this.currentTheme = Theme.current();
this.init();
}
init() {
this.createElement();
this.bindEvents();
this.updateTheme(this.currentTheme);
}
createElement() {
const modalId = `modal-${Date.now()}`;
const modalHTML = `
`;
document.body.insertAdjacentHTML('beforeend', modalHTML);
this.element = document.getElementById(modalId);
// Initialize Bootstrap modal
this.modal = new bootstrap.Modal(this.element, {
backdrop: this.options.backdrop,
keyboard: this.options.keyboard
});
}
bindEvents() {
// Theme change event
window.addEventListener('adminator:themeChanged', (event) => {
this.currentTheme = event.detail.theme;
this.updateTheme(this.currentTheme);
});
// Action button click
const actionButton = this.element.querySelector('.modal-action');
actionButton.addEventListener('click', () => {
if (this.options.onAction) {
this.options.onAction(this);
}
});
// Cleanup on hide
this.element.addEventListener('hidden.bs.modal', () => {
if (this.options.autoDestroy !== false) {
this.destroy();
}
});
}
updateTheme(theme) {
const content = this.element.querySelector('.modal-content');
// Apply theme-specific styles
content.style.backgroundColor = Theme.getCSSVar('--c-bkg-card');
content.style.color = Theme.getCSSVar('--c-text-base');
content.style.border = `1px solid ${Theme.getCSSVar('--c-border')}`;
// Update header
const header = this.element.querySelector('.modal-header');
header.style.borderBottom = `1px solid ${Theme.getCSSVar('--c-border')}`;
// Update footer
const footer = this.element.querySelector('.modal-footer');
footer.style.borderTop = `1px solid ${Theme.getCSSVar('--c-border')}`;
// Update close button for dark theme
const closeButton = this.element.querySelector('.btn-close');
if (theme === 'dark') {
closeButton.classList.add('btn-close-white');
} else {
closeButton.classList.remove('btn-close-white');
}
}
show() {
this.modal.show();
}
hide() {
this.modal.hide();
}
destroy() {
if (this.modal) {
this.modal.dispose();
}
if (this.element) {
this.element.remove();
}
}
}
// Usage
const modal = new ThemeModal({
title: 'Theme-Aware Modal',
content: 'This modal adapts to the current theme automatically.
',
size: 'lg',
onAction: (modalInstance) => {
console.log('Action button clicked!');
modalInstance.hide();
}
});
modal.show();
```
## React Hook Integration
### useTheme Hook
```javascript
import { useState, useEffect } from 'react';
// Custom hook for theme management
export function useTheme() {
const [theme, setTheme] = useState(() => {
// Initialize with current theme
return window.Theme ? window.Theme.current() : 'light';
});
const [isLoading, setIsLoading] = useState(!window.Theme);
useEffect(() => {
// Wait for Theme object to be available
if (!window.Theme) {
const checkTheme = () => {
if (window.Theme) {
setTheme(window.Theme.current());
setIsLoading(false);
} else {
setTimeout(checkTheme, 100);
}
};
checkTheme();
return;
}
const handleThemeChange = (event) => {
setTheme(event.detail.theme);
};
window.addEventListener('adminator:themeChanged', handleThemeChange);
return () => {
window.removeEventListener('adminator:themeChanged', handleThemeChange);
};
}, []);
const toggleTheme = () => {
if (window.Theme) {
window.Theme.toggle();
}
};
const setSpecificTheme = (newTheme) => {
if (window.Theme) {
window.Theme.apply(newTheme);
}
};
const getThemeColors = () => {
if (!window.Theme) return {};
return {
primary: window.Theme.getCSSVar('--c-primary'),
success: window.Theme.getCSSVar('--c-success'),
danger: window.Theme.getCSSVar('--c-danger'),
background: window.Theme.getCSSVar('--c-bkg-card'),
text: window.Theme.getCSSVar('--c-text-base'),
muted: window.Theme.getCSSVar('--c-text-muted'),
border: window.Theme.getCSSVar('--c-border')
};
};
return {
theme,
isLoading,
isDark: theme === 'dark',
isLight: theme === 'light',
toggle: toggleTheme,
setTheme: setSpecificTheme,
colors: getThemeColors()
};
}
// React component using the hook
export function ThemeAwareCard({ children, title }) {
const { theme, colors, isDark } = useTheme();
const cardStyle = {
backgroundColor: colors.background,
color: colors.text,
border: `1px solid ${colors.border}`,
borderRadius: '8px',
padding: '1.5rem',
boxShadow: isDark
? '0 2px 4px rgba(0, 0, 0, 0.3)'
: '0 2px 4px rgba(0, 0, 0, 0.1)'
};
const titleStyle = {
color: colors.text,
marginBottom: '1rem',
fontSize: '1.25rem',
fontWeight: '600'
};
return (
{title &&
{title} }
{children}
);
}
// Theme toggle button component
export function ThemeToggle() {
const { theme, toggle, isLoading } = useTheme();
if (isLoading) {
return Loading theme...
;
}
return (
{theme === 'dark' ? '☀️ Light' : '🌙 Dark'}
);
}
```
## Performance Optimization
### Throttled Theme Updates
For components with expensive rendering:
```javascript
class PerformantThemeComponent {
constructor(element) {
this.element = element;
this.updateTheme = this.throttle(this._updateTheme.bind(this), 100);
window.addEventListener('adminator:themeChanged', this.updateTheme);
}
throttle(func, limit) {
let inThrottle;
return function() {
const args = arguments;
const context = this;
if (!inThrottle) {
func.apply(context, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
}
}
_updateTheme(event) {
// Expensive theme update operations
this.recalculateLayout();
this.updateComplexVisuals();
}
recalculateLayout() {
// Complex layout calculations
}
updateComplexVisuals() {
// Expensive visual updates
}
}
```
---
**Next Steps:**
- Check out [Component APIs](../api/component-apis.md) for more integration options
- Explore [Advanced Patterns](advanced-patterns.md) for complex scenarios
- Review [Performance Optimization](../deployment/performance.md) for production tips
================================================
FILE: docs/examples.md
================================================
---
layout: default
title: Examples
nav_order: 5
has_children: true
---
# Examples
Practical examples and integration guides.
This section provides real-world examples of using Adminator in various scenarios and frameworks.
================================================
FILE: docs/getting-started/build-deployment.md
================================================
# Build & Deployment
This guide covers building Adminator for production and deploying it to various hosting platforms.
## Table of Contents
- Production Build
- Build Commands
- Build Output
- Build Optimization
- Deployment Options
- Static Hosting Platforms
- Server Deployment
- Environment Configuration
- Pre-Deployment Checklist
- Post-Deployment Verification
- Troubleshooting
- Next Steps
## Production Build
Before deploying, you need to create an optimized production build.
### Quick Build
```bash
npm run build
```
This creates an optimized, minified production build in the `dist/` directory.
## Build Commands
### Standard Production Build
```bash
npm run build
```
**Features:**
- Minified JavaScript and CSS
- Optimized assets
- Source maps disabled
- Production environment variables
- Compressed bundle sizes
### Minified Build
```bash
npm run release:minified
```
**Use case:** Maximum optimization for production deployment
**Features:**
- Aggressive minification
- Tree shaking
- Dead code elimination
- Smallest possible bundle size
### Unminified Build
```bash
npm run release:unminified
```
**Use case:** Debugging production issues
**Features:**
- Readable code
- No minification
- Easier to debug
- Larger file sizes
### Clean Build
```bash
# Clean and rebuild
npm run clean
npm run build
```
Removes the `dist/` directory before building to ensure a fresh build.
## Build Output
After running the build command, the `dist/` directory contains:
```
dist/
├── index.html # Main dashboard page
├── 404.html # Error pages
├── 500.html
├── signin.html # Authentication pages
├── signup.html
├── email.html # Application pages
├── compose.html
├── chat.html
├── calendar.html
├── charts.html
├── forms.html
├── buttons.html
├── ui.html
├── basic-table.html
├── datatable.html
├── google-maps.html
├── vector-maps.html
├── blank.html
└── assets/
├── bundle.[hash].js # Compiled JavaScript
├── styles.[hash].css # Compiled CSS
└── static/ # Images, fonts, etc.
├── fonts/
└── images/
```
### File Hashing
Production builds include content hashes in filenames (e.g., `bundle.a1b2c3d4.js`) for:
- Cache busting
- Long-term caching
- Version control
## Build Optimization
### Automatic Optimizations
The build process automatically applies:
1. **JavaScript Optimization**
- Minification with Terser
- Tree shaking (removes unused code)
- Code splitting
- ES6+ transpilation to ES5
2. **CSS Optimization**
- Minification with CSS Minimizer
- Autoprefixer for browser compatibility
- Unused CSS removal
- Critical CSS inlining (optional)
3. **Asset Optimization**
- Image compression
- Font subsetting
- SVG optimization
- Asset hashing
4. **Bundle Optimization**
- Gzip compression ready
- Brotli compression ready
- Chunk splitting
- Lazy loading support
### Bundle Size Analysis
To analyze your bundle size:
```bash
# Install webpack-bundle-analyzer
npm install --save-dev webpack-bundle-analyzer
# Add to webpack config and rebuild
npm run build
```
## Deployment Options
Adminator can be deployed to various platforms:
### 1. Static Hosting (Recommended)
- Netlify
- Vercel
- GitHub Pages
- Cloudflare Pages
- AWS S3 + CloudFront
- Azure Static Web Apps
### 2. Traditional Hosting
- Apache
- Nginx
- IIS
- Any web server
### 3. Cloud Platforms
- AWS (S3, EC2, Amplify)
- Google Cloud Platform
- Microsoft Azure
- DigitalOcean
## Static Hosting Platforms
### Netlify
**1. Install Netlify CLI:**
```bash
npm install -g netlify-cli
```
**2. Build your project:**
```bash
npm run build
```
**3. Deploy:**
```bash
netlify deploy --prod --dir=dist
```
**Or use Netlify's drag-and-drop:**
1. Go to [netlify.com](https://netlify.com)
2. Drag the `dist/` folder to deploy
3. Done!
**netlify.toml configuration:**
```toml
[build]
command = "npm run build"
publish = "dist"
[[redirects]]
from = "/*"
to = "/index.html"
status = 200
```
### Vercel
**1. Install Vercel CLI:**
```bash
npm install -g vercel
```
**2. Deploy:**
```bash
vercel --prod
```
**vercel.json configuration:**
```json
{
"buildCommand": "npm run build",
"outputDirectory": "dist",
"rewrites": [
{ "source": "/(.*)", "destination": "/" }
]
}
```
### GitHub Pages
**1. Install gh-pages:**
```bash
npm install --save-dev gh-pages
```
**2. Add deploy script to package.json:**
```json
{
"scripts": {
"deploy": "npm run build && gh-pages -d dist"
}
}
```
**3. Deploy:**
```bash
npm run deploy
```
**4. Configure GitHub Pages:**
- Go to repository Settings → Pages
- Select `gh-pages` branch
- Save
### Cloudflare Pages
**1. Connect your repository:**
- Go to [pages.cloudflare.com](https://pages.cloudflare.com)
- Connect your GitHub/GitLab repository
**2. Configure build settings:**
- Build command: `npm run build`
- Build output directory: `dist`
- Root directory: `/`
**3. Deploy:**
- Push to your repository
- Cloudflare automatically builds and deploys
### AWS S3 + CloudFront
**1. Build your project:**
```bash
npm run build
```
**2. Install AWS CLI:**
```bash
# Follow AWS CLI installation guide
aws configure
```
**3. Create S3 bucket and upload:**
```bash
# Create bucket
aws s3 mb s3://your-bucket-name
# Upload files
aws s3 sync dist/ s3://your-bucket-name --delete
# Enable static website hosting
aws s3 website s3://your-bucket-name --index-document index.html --error-document 404.html
```
**4. Set up CloudFront (optional but recommended):**
- Create CloudFront distribution
- Point to S3 bucket
- Configure caching rules
- Add custom domain (optional)
## Server Deployment
### Apache
**1. Build your project:**
```bash
npm run build
```
**2. Copy files to web root:**
```bash
cp -r dist/* /var/www/html/
```
**3. Configure .htaccess:**
```apache
# .htaccess in dist/
RewriteEngine On
RewriteBase /
RewriteRule ^index\.html$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.html [L]
# Enable Gzip compression
AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css text/javascript application/javascript
# Browser caching
ExpiresActive On
ExpiresByType image/jpg "access plus 1 year"
ExpiresByType image/jpeg "access plus 1 year"
ExpiresByType image/gif "access plus 1 year"
ExpiresByType image/png "access plus 1 year"
ExpiresByType text/css "access plus 1 month"
ExpiresByType application/javascript "access plus 1 month"
```
### Nginx
**1. Build your project:**
```bash
npm run build
```
**2. Copy files to web root:**
```bash
cp -r dist/* /usr/share/nginx/html/
```
**3. Configure nginx.conf:**
```nginx
server {
listen 80;
server_name your-domain.com;
root /usr/share/nginx/html;
index index.html;
# Gzip compression
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml text/javascript;
gzip_min_length 1000;
# Browser caching
location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# SPA fallback
location / {
try_files $uri $uri/ /index.html;
}
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
}
```
**4. Restart Nginx:**
```bash
sudo systemctl restart nginx
```
## Environment Configuration
### Environment Variables
Create environment-specific configurations:
**Development (.env.development):**
```env
NODE_ENV=development
API_URL=http://localhost:3000
DEBUG=true
```
**Production (.env.production):**
```env
NODE_ENV=production
API_URL=https://api.yourdomain.com
DEBUG=false
```
### Using Environment Variables
In your JavaScript:
```javascript
const apiUrl = process.env.API_URL || 'http://localhost:3000';
const isProduction = process.env.NODE_ENV === 'production';
```
## Pre-Deployment Checklist
Before deploying to production, verify:
### Code Quality
- [ ] All linting errors fixed (`npm run lint`)
- [ ] No console.log statements in production code
- [ ] All TODO/FIXME comments addressed
- [ ] Code reviewed and tested
### Build
- [ ] Production build succeeds (`npm run build`)
- [ ] No build warnings or errors
- [ ] Bundle sizes are acceptable
- [ ] All assets are included
### Testing
- [ ] All pages load correctly
- [ ] Navigation works properly
- [ ] Forms submit correctly
- [ ] Charts and visualizations display
- [ ] Dark mode toggle works
- [ ] Responsive design tested on mobile
### Performance
- [ ] Images optimized
- [ ] Lazy loading implemented where needed
- [ ] Bundle size optimized
- [ ] Caching configured
### Security
- [ ] No sensitive data in code
- [ ] API keys in environment variables
- [ ] HTTPS configured
- [ ] Security headers set
- [ ] CORS configured properly
### SEO (if applicable)
- [ ] Meta tags added
- [ ] Page titles set
- [ ] Alt text for images
- [ ] Sitemap generated
- [ ] robots.txt configured
### Browser Compatibility
- [ ] Tested in Chrome/Edge
- [ ] Tested in Firefox
- [ ] Tested in Safari
- [ ] Tested on mobile devices
## Post-Deployment Verification
After deployment, verify:
1. **Site Accessibility**
- Visit your deployed URL
- Check all pages load
- Test navigation
2. **Functionality**
- Test interactive features
- Verify forms work
- Check API connections
3. **Performance**
- Run Lighthouse audit
- Check page load times
- Verify asset loading
4. **Mobile Responsiveness**
- Test on actual devices
- Check responsive breakpoints
- Verify touch interactions
5. **Console Errors**
- Open browser DevTools
- Check for JavaScript errors
- Verify no 404s for assets
## Troubleshooting
### Build Fails
```bash
# Clear cache and rebuild
npm run clean
rm -rf node_modules package-lock.json
npm install
npm run build
```
### Assets Not Loading
- Check file paths are relative
- Verify assets are in `dist/` folder
- Check server configuration
- Verify CORS settings
### Blank Page After Deployment
- Check browser console for errors
- Verify all files uploaded
- Check base URL configuration
- Verify server routing for SPA
### Styles Not Applied
- Check CSS file is loaded
- Verify CSS file path
- Check for CSS conflicts
- Clear browser cache
### 404 Errors
- Configure server for SPA routing
- Add `.htaccess` or nginx config
- Verify all HTML files uploaded
## Performance Optimization
### Enable Compression
Most hosting platforms enable Gzip/Brotli automatically. Verify:
```bash
# Check if Gzip is enabled
curl -H "Accept-Encoding: gzip" -I https://your-domain.com
```
### CDN Configuration
Use a CDN for faster global delivery:
- Cloudflare
- AWS CloudFront
- Fastly
- KeyCDN
### Caching Strategy
Set appropriate cache headers:
- HTML: No cache or short cache
- CSS/JS: Long cache (1 year) with hashing
- Images: Long cache (1 year)
- Fonts: Long cache (1 year)
## Continuous Deployment
### GitHub Actions
Create `.github/workflows/deploy.yml`:
```yaml
name: Deploy to Production
on:
push:
branches: [ main ]
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup Node.js
uses: actions/setup-node@v2
with:
node-version: '18'
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
- name: Deploy to Netlify
uses: netlify/actions/cli@master
env:
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}
with:
args: deploy --prod --dir=dist
```
## Next Steps
Congratulations on deploying Adminator! 🎉
Continue learning:
1. **[Theme System](../customization/theme-system.md)** - Customize colors and themes
2. **[API Reference](../api/theme-api.md)** - JavaScript API documentation
3. **[Examples](../examples/theme-integration.md)** - Integration examples
---
**Need Help?** Check the [main README](../../README.md) or [open an issue](https://github.com/puikinsh/Adminator-admin-dashboard/issues).
================================================
FILE: docs/getting-started/development.md
================================================
# Development Workflow
This guide covers the development workflow for working with Adminator, including running the development server, making changes, and best practices.
## Table of Contents
- Quick Start
- Development Server
- Available npm Scripts
- Making Changes
- Hot Module Replacement
- Code Quality & Linting
- Debugging
- Working with Components
- Working with Styles
- Best Practices
- Common Issues
- Next Steps
## Quick Start
After [installation](installation.md), start the development server:
```bash
npm start
```
Your application will be available at **http://localhost:4000**
## Development Server
### Standard Development Server
The standard development server includes hot module replacement (HMR) for instant updates:
```bash
npm start
```
**Features:**
- Hot module replacement (HMR)
- Automatic browser refresh
- Source maps for debugging
- Fast rebuild times
- Runs on port 4000
### Development Server with Dashboard
For enhanced development experience with visual feedback:
```bash
npm run dev
```
**Additional Features:**
- Visual webpack dashboard
- Real-time build statistics
- Bundle size analysis
- Build time metrics
- Module dependency graph
## Available npm Scripts
### Development
| Command | Description |
|---------|-------------|
| `npm start` | Start development server with HMR |
| `npm run dev` | Start development server with webpack dashboard |
| `npm run clean` | Clean the `dist/` directory |
### Production Build
| Command | Description |
|---------|-------------|
| `npm run build` | Production build (optimized, minified) |
| `npm run release:minified` | Production build with minification |
| `npm run release:unminified` | Production build without minification (for debugging) |
| `npm run preview` | Preview production build locally |
### Code Quality
| Command | Description |
|---------|-------------|
| `npm run lint` | Run all linters (JavaScript + SCSS) |
| `npm run lint:js` | Lint JavaScript files with ESLint |
| `npm run lint:scss` | Lint SCSS files with Stylelint |
## Making Changes
### File Watching
The development server automatically watches for changes in:
- **HTML files** (`src/*.html`)
- **JavaScript files** (`src/assets/scripts/**/*.js`)
- **SCSS files** (`src/assets/styles/**/*.scss`)
- **Static assets** (`src/assets/static/**/*`)
Changes are automatically compiled and the browser refreshes.
### Workflow
1. **Start the development server**
```bash
npm start
```
2. **Make your changes** in the `src/` directory
3. **Save the file** - Changes are automatically detected
4. **Browser refreshes** - See your changes instantly
## Hot Module Replacement (HMR)
HMR allows modules to be updated without a full page reload, preserving application state.
### What Gets Hot Reloaded?
- ✅ **JavaScript modules** - Component updates without page reload
- ✅ **SCSS/CSS** - Style updates without page reload
- ⚠️ **HTML files** - Requires full page reload
- ⚠️ **Configuration files** - Requires server restart
### HMR Benefits
- Faster development cycle
- Preserves application state
- Instant visual feedback
- Better debugging experience
## Code Quality & Linting
### JavaScript Linting (ESLint)
Adminator uses ESLint 9.x with flat configuration:
```bash
# Lint all JavaScript files
npm run lint:js
# Auto-fix issues (if possible)
npx eslint ./src --fix
```
**Configuration:** `eslint.config.mjs`
**Rules:**
- ES6+ modern JavaScript
- No jQuery patterns
- Consistent code style
- Import/export validation
### SCSS Linting (Stylelint)
Maintain consistent SCSS code style:
```bash
# Lint all SCSS files
npm run lint:scss
# Auto-fix issues (if possible)
npx stylelint "./src/**/*.scss" --fix
```
**Configuration:** `.stylelintrc.json`
### Running All Linters
```bash
npm run lint
```
This runs both JavaScript and SCSS linters in sequence.
## Debugging
### Source Maps
Development builds include source maps for easier debugging:
1. Open browser DevTools (F12)
2. Navigate to Sources tab
3. Find your original source files under `webpack://`
4. Set breakpoints and debug as normal
### Console Logging
The application includes minimal console output in production. For development debugging:
```javascript
// Development-only logging
if (process.env.NODE_ENV !== 'production') {
console.log('Debug info:', data);
}
```
### Browser DevTools
**Recommended Extensions:**
- React Developer Tools (if using React components)
- Vue.js devtools (if using Vue components)
- Redux DevTools (if using Redux)
## Working with Components
### Creating a New Component
1. **Create component file** in `src/assets/scripts/components/`:
```javascript
// src/assets/scripts/components/MyComponent.js
class MyComponent {
constructor(element) {
this.element = element;
this.init();
}
init() {
// Initialize component
this.setupEventListeners();
}
setupEventListeners() {
// Add event listeners
}
destroy() {
// Cleanup
}
}
export default MyComponent;
```
2. **Import and use** in `app.js`:
```javascript
import MyComponent from '@/components/MyComponent';
// Initialize
const myComponent = new MyComponent(document.querySelector('.my-component'));
```
3. **Add component styles** in `src/assets/styles/spec/components/`:
```scss
// src/assets/styles/spec/components/myComponent.scss
.my-component {
// Component styles
}
```
4. **Import styles** in `src/assets/styles/spec/components/index.scss`:
```scss
@import 'myComponent';
```
### Component Best Practices
- Use ES6 classes for components
- Keep components focused and single-purpose
- Implement `destroy()` method for cleanup
- Use webpack aliases (`@/components`, `@/utils`)
- Follow existing naming conventions
## Working with Styles
### SCSS Architecture
Adminator follows ITCSS (Inverted Triangle CSS) methodology:
```
styles/
├── settings/ # Variables, config
├── tools/ # Mixins, functions
├── generic/ # Reset, normalize
├── components/ # UI components
├── utils/ # Utility classes
└── vendor/ # Third-party styles
```
### Adding New Styles
1. **Component styles** → `src/assets/styles/spec/components/`
2. **Page-specific styles** → `src/assets/styles/spec/screens/`
3. **Utility classes** → `src/assets/styles/spec/utils/`
### Using CSS Variables
Adminator uses CSS custom properties for theming:
```scss
.my-component {
background: var(--c-bkg-card);
color: var(--c-text-base);
border: 1px solid var(--c-border);
}
```
**Available variables:** See `src/assets/styles/spec/utils/theme.css`
### Dark Mode Support
Ensure your components support dark mode:
```scss
.my-component {
background: var(--c-bkg-card); // Auto-adjusts for dark mode
// Or use data attribute
[data-theme="dark"] & {
background: #1f2937;
}
}
```
## Best Practices
### Code Organization
- ✅ Keep files small and focused
- ✅ Use meaningful file and variable names
- ✅ Group related functionality
- ✅ Follow existing project structure
- ✅ Use webpack aliases for imports
### JavaScript
- ✅ Use modern ES6+ features
- ✅ Avoid jQuery patterns
- ✅ Use vanilla JavaScript DOM APIs
- ✅ Implement proper error handling
- ✅ Add JSDoc comments for complex functions
### SCSS
- ✅ Use variables for colors and spacing
- ✅ Follow BEM naming convention (optional)
- ✅ Keep selectors shallow (max 3 levels)
- ✅ Use mixins for repeated patterns
- ✅ Support dark mode with CSS variables
### Performance
- ✅ Minimize DOM manipulations
- ✅ Use event delegation
- ✅ Debounce/throttle frequent events
- ✅ Lazy load heavy components
- ✅ Optimize images and assets
### Accessibility
- ✅ Use semantic HTML
- ✅ Add ARIA labels where needed
- ✅ Ensure keyboard navigation
- ✅ Maintain color contrast ratios
- ✅ Test with screen readers
## Common Issues
### Port Already in Use
If port 4000 is already in use:
```bash
# Kill the process using port 4000 (Windows)
netstat -ano | findstr :4000
taskkill /PID /F
# Or change the port in webpack/devServer.js
```
### Changes Not Reflecting
1. **Hard refresh** the browser (Ctrl+F5)
2. **Clear browser cache**
3. **Restart development server**
4. **Check for JavaScript errors** in console
5. **Verify file is being watched** (check terminal output)
### Build Errors
```bash
# Clean and rebuild
npm run clean
npm install
npm start
```
### Linting Errors
```bash
# Auto-fix common issues
npx eslint ./src --fix
npx stylelint "./src/**/*.scss" --fix
# Check remaining issues
npm run lint
```
### Module Not Found
```bash
# Reinstall dependencies
rm -rf node_modules package-lock.json
npm install
```
## Development Tips
### 1. Use the Webpack Dashboard
```bash
npm run dev
```
Provides visual feedback on build performance and bundle size.
### 2. Keep the Console Clean
Fix warnings and errors as they appear to maintain code quality.
### 3. Test in Multiple Browsers
- Chrome/Edge (Chromium)
- Firefox
- Safari (if on macOS)
- Mobile browsers (responsive mode)
### 4. Use Browser DevTools
- **Elements tab** - Inspect and modify DOM/CSS
- **Console tab** - Debug JavaScript
- **Network tab** - Monitor requests
- **Performance tab** - Profile performance
- **Application tab** - Check localStorage/theme
### 5. Commit Often
Make small, focused commits with clear messages:
```bash
git add .
git commit -m "feat: add new dashboard widget"
git push
```
## Next Steps
Now that you understand the development workflow:
1. **[Customize Themes](../customization/theme-system.md)** - Set up dark mode and theming
2. **[Build for Production](build-deployment.md)** - Deploy your application
3. **[API Reference](../api/theme-api.md)** - JavaScript API documentation
4. **[Project Structure](project-structure.md)** - Review the codebase structure
---
**Need Help?** Check the [main README](../../README.md) or [open an issue](https://github.com/puikinsh/Adminator-admin-dashboard/issues).
================================================
FILE: docs/getting-started/installation.md
================================================
---
layout: default
title: Installation
nav_order: 1
parent: Getting Started
---
# Installation Guide
{: .no_toc }
## Table of contents
{: .no_toc .text-delta }
1. TOC
{:toc}
---
This guide will help you get Adminator up and running on your local machine.
## Prerequisites
Before installing Adminator, ensure you have the following installed:
### Required Software
- **Node.js** (v18.12.0 or higher)
- Download from [nodejs.org](https://nodejs.org/)
- Verify installation: `node --version`
- **npm** (comes with Node.js) or **Yarn**
- Verify npm: `npm --version`
- **Git** for version control
- Download from [git-scm.com](https://git-scm.com/)
### System Requirements
- **Operating System**: Windows 10+, macOS 10.14+, or Linux
- **RAM**: Minimum 4GB (8GB recommended for development)
- **Storage**: 500MB free space for dependencies
## Installation Methods
### Method 1: Clone from GitHub (Recommended)
```bash
# Clone the repository
git clone https://github.com/puikinsh/Adminator-admin-dashboard.git
# Navigate to the project directory
cd Adminator-admin-dashboard
# Install dependencies
npm install
# Start development server
npm start
```
### Method 2: Download ZIP
1. Visit the [GitHub repository](https://github.com/puikinsh/Adminator-admin-dashboard)
2. Click **"Code"** → **"Download ZIP"**
3. Extract the downloaded file
4. Open terminal in the extracted folder
5. Run `npm install` and `npm start`
### Method 3: Use with Existing Project
```bash
# Add Adminator to your project
npm install --save adminator
# Or download specific release
wget https://github.com/puikinsh/Adminator-admin-dashboard/archive/v2.6.0.zip
```
## Verification
After installation, verify everything works:
### 1. Development Server
```bash
npm start
```
**Expected Output:**
```
> adminator@2.6.0 start
> webpack server
✓ Project is running at: http://localhost:4000/
✓ webpack compiled successfully
```
### 2. Build Process
```bash
npm run build
```
**Expected Output:**
```
> adminator@2.6.0 build
> npm run clean && cross-env webpack
✓ webpack compiled successfully in [time]ms
```
### 3. Access the Application
Open your browser and navigate to:
- **Local**: `http://localhost:4000`
- **Network**: `http://[your-ip]:4000`
You should see the Adminator dashboard with:
- ✅ Clean interface loading properly
- ✅ Dark/Light mode toggle in the header
- ✅ All components rendering correctly
- ✅ No console errors
## Troubleshooting
### Common Issues
#### Port Already in Use
```bash
# Error: EADDRINUSE: address already in use :::4000
# Solution: Kill the process using port 4000
sudo lsof -ti:4000 | xargs kill -9
# Or use a different port
PORT=3000 npm start
```
#### Node Version Issues
```bash
# Check your Node.js version
node --version
# If version is below 18.12.0, update Node.js
# Use nvm (recommended):
nvm install 18
nvm use 18
```
#### Permission Errors
```bash
# On macOS/Linux, you might need sudo for global packages
sudo npm install -g npm@latest
# Better solution: Fix npm permissions
npm config set prefix ~/.npm-global
export PATH=~/.npm-global/bin:$PATH
```
#### Missing Dependencies
```bash
# Clear npm cache and reinstall
npm cache clean --force
rm -rf node_modules package-lock.json
npm install
```
#### Build Errors
```bash
# Check for conflicting global packages
npm list -g --depth=0
# Update npm and dependencies
npm update
npm audit fix
```
### Getting Help
If you encounter issues:
1. **Check the [GitHub Issues](https://github.com/puikinsh/Adminator-admin-dashboard/issues)**
2. **Search existing solutions**
3. **Create a new issue** with:
- Operating system and version
- Node.js and npm versions
- Complete error message
- Steps to reproduce
## Next Steps
After successful installation:
1. **[Explore Project Structure](project-structure.md)** - Understand the codebase
2. **[Development Workflow](development.md)** - Learn the development process
3. **[Customize Themes](../customization/theme-system.md)** - Set up dark mode and theming
4. **[Build for Production](build-deployment.md)** - Deploy your application
---
**Installation Complete!** 🎉 You're ready to start building with Adminator.
================================================
FILE: docs/getting-started/project-structure.md
================================================
# Project Structure
This guide explains the folder structure and organization of the Adminator admin dashboard template.
## Overview
Adminator follows a modern, modular architecture with clear separation of concerns. The project is organized into source files, build configuration, and documentation.
```
adminator-admin-dashboard/
├── src/ # Source files
├── dist/ # Built/compiled files (generated)
├── webpack/ # Build configuration
├── docs/ # Documentation
├── node_modules/ # Dependencies (generated)
└── Configuration files
```
## Root Directory
### Configuration Files
| File | Purpose |
|------|---------|
| `package.json` | Node.js dependencies and npm scripts |
| `webpack.config.js` | Webpack entry point |
| `.babelrc` | Babel ES6+ transpiler configuration |
| `eslint.config.mjs` | ESLint 9.x flat configuration for code linting |
| `.stylelintrc.json` | Stylelint configuration for SCSS/CSS linting |
| `.editorconfig` | Editor settings for consistent code style |
| `browserslist` | Target browser versions for compilation |
| `.gitignore` | Git ignore patterns |
| `.gitattributes` | Git attributes configuration |
| `.nvmrc` | Node.js version specification |
### Documentation Files
| File | Purpose |
|------|---------|
| `README.md` | Main project documentation |
| `CHANGELOG.md` | Version history and release notes |
| `LICENSE` | MIT license information |
| `CODE_OF_CONDUCT.md` | Community guidelines |
## Source Directory (`src/`)
The `src/` directory contains all template source files that are compiled into the final application.
### HTML Templates
Located directly in `src/`, these are the template pages:
```
src/
├── index.html # Main dashboard page
├── blank.html # Blank page template
├── 404.html # 404 error page
├── 500.html # 500 error page
├── signin.html # Sign in page
├── signup.html # Sign up page
├── email.html # Email inbox
├── compose.html # Email compose
├── chat.html # Chat application
├── calendar.html # Calendar view
├── charts.html # Charts showcase
├── forms.html # Form elements
├── buttons.html # Button styles
├── ui.html # UI elements showcase
├── basic-table.html # Basic table
├── datatable.html # Data table with features
├── google-maps.html # Google Maps integration
└── vector-maps.html # Vector maps
```
### Assets Directory (`src/assets/`)
Contains all JavaScript, styles, images, and fonts.
```
src/assets/
├── scripts/ # JavaScript files
├── styles/ # SCSS stylesheets
└── static/ # Static assets (images, fonts)
```
## JavaScript Structure (`src/assets/scripts/`)
Modern, jQuery-free vanilla JavaScript architecture.
### Main Application
```
scripts/
├── app.js # Main application entry point
├── index.js # Module exports
└── components/ # Reusable components
├── Sidebar.js # Sidebar navigation component
└── Chart.js # Chart component wrapper
```
### Feature Modules
Each feature has its own directory with an `index.js` entry point:
```
scripts/
├── charts/ # Chart implementations
│ ├── chartJS/ # Chart.js integration
│ ├── easyPieChart/ # Pie chart component
│ └── sparkline/ # Sparkline mini charts
├── chat/ # Chat application logic
├── email/ # Email application logic
├── fullcalendar/ # Calendar integration
├── googleMaps/ # Google Maps integration
├── vectorMaps/ # Vector maps integration
├── datatable/ # Data table functionality
├── datepicker/ # Date picker component
├── masonry/ # Masonry grid layout
├── popover/ # Popover components
├── scrollbar/ # Custom scrollbar
├── search/ # Search functionality
├── sidebar/ # Sidebar behavior
├── skycons/ # Weather icons
└── ui/ # UI components
```
### Utilities and Constants
```
scripts/
├── utils/ # Utility functions
│ ├── dom.js # DOM manipulation helpers
│ ├── date.js # Date utilities (Day.js wrapper)
│ ├── theme.js # Theme management (dark/light mode)
│ └── index.js # Utility exports
└── constants/ # Application constants
└── colors.js # Color definitions
```
## Styles Structure (`src/assets/styles/`)
SCSS-based styling with modular architecture following ITCSS methodology.
```
styles/
├── index.scss # Main style entry point
├── spec/ # Custom styles
│ ├── components/ # Component styles
│ │ ├── sidebar.scss
│ │ ├── topbar.scss
│ │ ├── footer.scss
│ │ ├── forms.scss
│ │ ├── loader.scss
│ │ ├── masonry.scss
│ │ ├── pageContainer.scss
│ │ ├── progressBar.scss
│ │ └── easyPieChart.scss
│ ├── generic/ # Base/reset styles
│ │ └── base.scss
│ ├── screens/ # Page-specific styles
│ │ ├── chat.scss
│ │ └── email.scss
│ ├── settings/ # Variables and configuration
│ │ ├── baseColors.scss
│ │ ├── materialColors.scss
│ │ ├── borders.scss
│ │ ├── breakpoints.scss
│ │ └── fonts.scss
│ ├── tools/ # Mixins and functions
│ │ └── mixins/
│ └── utils/ # Utility classes
│ ├── colors.scss
│ ├── theme.css # CSS variables for dark mode
│ └── layout/ # Layout helpers
└── vendor/ # Third-party plugin styles
```
## Static Assets (`src/assets/static/`)
```
static/
├── fonts/ # Icon fonts
│ ├── themify-icons/ # Themify Icons
│ └── font-awesome/ # Font Awesome (if used)
└── images/ # Images and graphics
├── logo.svg # Application logo
├── bg.jpg # Background images
├── 404.png # Error page illustrations
└── 500.png
```
## Webpack Configuration (`webpack/`)
Modular webpack configuration split into logical parts:
```
webpack/
├── config.js # Main webpack configuration
├── manifest.js # Build constants and paths
├── devServer.js # Development server settings
├── plugins/ # Webpack plugins configuration
│ ├── index.js
│ ├── html.js # HTML generation
│ ├── copy.js # File copying
│ ├── extractCSS.js # CSS extraction
│ └── ...
└── rules/ # Webpack loaders configuration
├── index.js
├── javascript.js # Babel loader
├── styles.js # SCSS/CSS loaders
├── fonts.js # Font loaders
└── images.js # Image loaders
```
## Build Output (`dist/`)
Generated directory containing compiled production files:
```
dist/
├── index.html # Compiled HTML files
├── *.html # All other pages
├── assets/
│ ├── bundle.[hash].js # Compiled JavaScript
│ ├── styles.[hash].css # Compiled CSS
│ └── static/ # Copied static assets
└── ...
```
## Documentation (`docs/`)
GitHub Pages documentation site:
```
docs/
├── index.md # Documentation home
├── getting-started/ # Getting started guides
│ ├── installation.md
│ └── project-structure.md (this file)
├── customization/ # Customization guides
│ └── theme-system.md
├── api/ # API documentation
│ └── theme-api.md
└── examples/ # Code examples
└── theme-integration.md
```
## Key Architecture Decisions
### 1. **jQuery-Free**
All JavaScript is written in modern vanilla JavaScript (ES6+) without jQuery dependency, resulting in smaller bundle size and better performance.
### 2. **Component-Based**
JavaScript is organized into reusable components (`Sidebar`, `Chart`, etc.) using ES6 classes.
### 3. **Modular SCSS**
Styles follow ITCSS methodology with clear separation of settings, tools, generic, components, and utilities.
### 4. **Modern Build System**
Webpack 5 with Babel for ES6+ transpilation, SCSS compilation, and asset optimization.
### 5. **Dark Mode Support**
CSS custom properties (variables) enable seamless theme switching between light and dark modes.
## File Naming Conventions
- **JavaScript**: camelCase for files and classes (`app.js`, `Sidebar.js`)
- **SCSS**: kebab-case for files (`sidebar.scss`, `page-container.scss`)
- **HTML**: kebab-case for files (`basic-table.html`, `google-maps.html`)
- **Components**: PascalCase for class names (`Sidebar`, `ChartComponent`)
## Import Paths
The project uses webpack aliases for cleaner imports:
```javascript
// Instead of: import Sidebar from '../../components/Sidebar'
import Sidebar from '@/components/Sidebar';
// Available aliases:
// @/ -> src/
// @/components -> src/assets/scripts/components/
// @/utils -> src/assets/scripts/utils/
// @/constants -> src/assets/scripts/constants/
```
## Adding New Features
### Adding a New Page
1. Create HTML file in `src/` (e.g., `my-page.html`)
2. Add page-specific styles in `src/assets/styles/spec/screens/`
3. Add page-specific JavaScript in `src/assets/scripts/`
4. The build system will automatically include it
### Adding a New Component
1. Create component file in `src/assets/scripts/components/`
2. Export the component class
3. Import and use in `app.js` or other modules
4. Add component styles in `src/assets/styles/spec/components/`
### Adding a New Utility
1. Create utility file in `src/assets/scripts/utils/`
2. Export functions/classes
3. Import from `@/utils/` in other files
## Next Steps
Now that you understand the project structure:
1. **[Development Workflow](development.md)** - Learn the development process
2. **[Customize Themes](../customization/theme-system.md)** - Set up dark mode and theming
3. **[Build for Production](build-deployment.md)** - Deploy your application
4. **[API Reference](../api/theme-api.md)** - JavaScript API documentation
---
**Need Help?** Check the [main README](../../README.md) or [open an issue](https://github.com/puikinsh/Adminator-admin-dashboard/issues).
================================================
FILE: docs/getting-started.md
================================================
---
layout: default
title: Getting Started
nav_order: 2
has_children: true
---
# Getting Started
Everything you need to know to start building with Adminator.
This section covers installation, project setup, development workflow, and deployment options.
================================================
FILE: docs/index.md
================================================
---
layout: default
title: Home
nav_order: 1
description: "Adminator Bootstrap 5 Admin Dashboard with Dark Mode"
permalink: /
---
# Adminator Documentation
{: .fs-9 }
Complete guide for the Bootstrap 5 Admin Dashboard Template with comprehensive Dark Mode system
{: .fs-6 .fw-300 }
[Get Started Now](getting-started/installation){: .btn .btn-primary .fs-5 .mb-4 .mb-md-0 .mr-2 }
[View on GitHub](https://github.com/puikinsh/Adminator-admin-dashboard){: .btn .fs-5 .mb-4 .mb-md-0 }
---
## ✨ What's New in v2.6.0
🌗 **Complete Dark Mode System** - Intelligent theme switching with OS preference detection
⚡ **Smart Theme Toggle** - Bootstrap-based switch with instant updates
🎨 **CSS Variables Architecture** - Semantic color system for easy customization
📊 **Component Integration** - All charts, calendars, and maps are theme-aware
---
## 🚀 Quick Start
```bash
# Clone the repository
git clone https://github.com/puikinsh/Adminator-admin-dashboard.git
# Install dependencies
npm install
# Start development server
npm start
```
Visit `http://localhost:4000` to see your dashboard!
---
## 📚 Documentation Sections
### Getting Started
Learn how to install, configure, and deploy Adminator with our comprehensive setup guides.
[Installation Guide →](getting-started/installation){: .btn .btn-outline }
### Dark Mode & Theming
Discover the powerful dark mode system with CSS variables and theme switching capabilities.
[Theme System →](customization/theme-system){: .btn .btn-outline }
### API Reference
Complete JavaScript API documentation for theme management and component integration.
[API Documentation →](api/theme-api){: .btn .btn-outline }
### Examples & Integration
Real-world examples and integration guides for popular frameworks and use cases.
[View Examples →](examples/theme-integration){: .btn .btn-outline }
---
## 🎯 Key Features
| Feature | Description |
|:--------|:------------|
| **Bootstrap 5** | Latest Bootstrap framework with modern components |
| **Dark Mode** | Comprehensive dark theme with intelligent switching |
| **Responsive** | Mobile-first design that works on all devices |
| **Chart Integration** | Chart.js with theme-aware color schemes |
| **Calendar Support** | FullCalendar with dark mode styling |
| **Vector Maps** | Interactive maps with custom color palettes |
| **Clean Code** | Well-organized, documented, and maintainable |
---
## 🌟 Live Demo
Experience Adminator's features in action:
### Light Mode

### Dark Mode

[Try Live Demo](https://colorlib.com/polygon/adminator/index.html){: .btn .btn-green .fs-5 }
---
## 🤝 Contributing
We welcome contributions! Please read our [contributing guidelines](contributing/) before submitting pull requests.
---
## 📄 License
Adminator is released under the [MIT License](https://github.com/puikinsh/Adminator-admin-dashboard/blob/master/LICENSE).
---
**Ready to build amazing dashboards?** Start with our [installation guide](getting-started/installation) or explore the [dark mode features](customization/theme-system)!
================================================
FILE: eslint.config.mjs
================================================
import globals from "globals";
import babelParser from "@babel/eslint-parser";
import js from "@eslint/js";
export default [
{
files: ["**/*.js", "**/*.mjs", "**/*.jsx"],
languageOptions: {
globals: {
...globals.browser,
...globals.node,
},
parser: babelParser,
ecmaVersion: 2018,
sourceType: "module",
parserOptions: {
requireConfigFile: false,
babelOptions: {
babelrc: false,
configFile: false,
presets: ["@babel/preset-env"],
},
ecmaFeatures: {
modules: true,
destructuring: true,
classes: true,
forOf: true,
blockBindings: true,
arrowFunctions: true,
},
},
},
settings: {
ecmascript: 7,
},
rules: {
// Start with ESLint recommended rules
...js.configs.recommended.rules,
// Apply our custom overrides (keeping original project preferences)
"arrow-body-style": 0,
"prefer-arrow-callback": 0,
"arrow-parens": 0,
"no-param-reassign": 0,
"no-new": 0,
"consistent-return": 0,
"key-spacing": 0,
"no-multi-spaces": 0,
"no-underscore-dangle": 0,
"one-var": 0,
"global-require": 0,
"class-methods-use-this": 0,
"comma-dangle": ["error", {
arrays: "always-multiline",
objects: "always-multiline",
imports: "always-multiline",
exports: "always-multiline",
functions: "never",
}],
"func-names": 0,
"function-paren-newline": 0,
"indent": ["error", 2],
"new-cap": 0,
"no-plusplus": 0,
"no-return-assign": 0,
"quote-props": 0,
"template-curly-spacing": 0,
"no-unused-expressions": 0,
// Import rules (basic ones that don't require the import plugin)
"no-duplicate-imports": "error",
// Line ending for Unix/macOS (updated for current platform)
"linebreak-style": ["error", "unix"],
// Basic ES6+ rules that replace some airbnb functionality
"prefer-const": "warn",
"no-var": "error",
"prefer-template": "warn",
"object-shorthand": "warn",
},
},
{
// Global ignores
ignores: ["dist/**/*", "node_modules/**/*", "*.min.js"],
},
];
================================================
FILE: jsconfig.json
================================================
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "node",
"baseUrl": "./src",
"paths": {
"@/*": ["*"],
"@components/*": ["assets/scripts/components/*"],
"@utils/*": ["assets/scripts/utils/*"],
"@charts/*": ["assets/scripts/charts/*"],
"@styles/*": ["assets/styles/*"]
},
"checkJs": true,
"strict": false,
"noEmit": true,
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"resolveJsonModule": true,
"typeRoots": ["./types", "./node_modules/@types"]
},
"include": [
"src/**/*.js",
"types/**/*.d.ts"
],
"exclude": [
"node_modules",
"dist"
]
}
================================================
FILE: package.json
================================================
{
"name": "adminator-admin-dashboard",
"version": "3.0.0",
"private": false,
"description": "Modern jQuery-free Bootstrap 5 Admin Dashboard Template with Dark Mode",
"main": "dist/index.html",
"keywords": [
"admin",
"dashboard",
"template",
"bootstrap",
"bootstrap5",
"jquery-free",
"vanilla-js",
"dark-mode",
"responsive",
"mobile-first",
"scss",
"webpack",
"modern",
"ui-kit",
"admin-panel",
"charts",
"datatable"
],
"author": "Aigars Silkalns (https://colorlib.com)",
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/puikinsh/Adminator-admin-dashboard.git"
},
"bugs": {
"url": "https://github.com/puikinsh/Adminator-admin-dashboard/issues"
},
"homepage": "https://puikinsh.github.io/Adminator-admin-dashboard/",
"files": [
"dist/**/*",
"src/**/*",
"webpack.config.js",
"CLAUDE.md",
"CHANGELOG.md",
"README.md"
],
"engines": {
"node": ">=14.0.0"
},
"scripts": {
"start": "webpack server",
"dev": "webpack-dashboard -t 'Project' -- webpack server",
"clean": "shx rm -rf ./dist",
"build": "npm run clean && cross-env webpack",
"build:analyze": "npm run clean && cross-env ANALYZE=true NODE_ENV=production webpack",
"release:minified": "npm run clean && NODE_ENV=production MINIFY=true cross-env webpack",
"release:unminified": "npm run clean && NODE_ENV=production MINIFY=false cross-env webpack",
"preview": "cross-env webpack server",
"lint:js": "eslint ./src ./webpack ./*.js -f table --ext .js --ext .jsx",
"lint:scss": "stylelint ./src/**/*.scss",
"lint": "npm run lint:js && npm run lint:scss",
"test": "vitest",
"test:run": "vitest run",
"test:coverage": "vitest run --coverage",
"prepublishOnly": "npm run lint && npm run build",
"postpublish": "echo 'Package published successfully! View at: https://www.npmjs.com/package/adminator-admin-dashboard'"
},
"devDependencies": {
"@babel/core": "^7.28.6",
"@babel/eslint-parser": "^7.28.6",
"@babel/plugin-transform-runtime": "^7.28.5",
"@babel/preset-env": "^7.28.6",
"@eslint/js": "^9.39.2",
"@vitest/coverage-v8": "^4.0.17",
"babel-loader": "^10.0.0",
"case-sensitive-paths-webpack-plugin": "^2.4.0",
"copy-webpack-plugin": "^13.0.1",
"cross-env": "^10.1.0",
"css-loader": "^7.1.2",
"css-minimizer-webpack-plugin": "^7.0.4",
"eslint": "^9.39.2",
"eslint-formatter-table": "^7.32.1",
"globals": "^17.0.0",
"html-webpack-plugin": "^5.6.5",
"jsdom": "^27.4.0",
"mini-css-extract-plugin": "^2.9.4",
"postcss": "^8.5.6",
"postcss-loader": "^8.2.0",
"postcss-preset-env": "^10.6.1",
"sass": "^1.97.2",
"sass-loader": "^16.0.6",
"shx": "^0.4.0",
"style-loader": "^4.0.0",
"stylelint": "^16.26.1",
"stylelint-config-standard": "^39.0.1",
"stylelint-config-standard-scss": "^16.0.0",
"vitest": "^4.0.17",
"webpack": "^5.104.1",
"webpack-bundle-analyzer": "^5.1.1",
"webpack-cli": "^6.0.1",
"webpack-dashboard": "^3.3.8",
"webpack-dev-server": "^5.2.3"
},
"dependencies": {
"@babel/runtime": "^7.28.6",
"@fullcalendar/core": "^6.1.20",
"@fullcalendar/daygrid": "^6.1.20",
"@fullcalendar/interaction": "^6.1.20",
"@fullcalendar/list": "^6.1.20",
"@fullcalendar/timegrid": "^6.1.20",
"@popperjs/core": "^2.11.8",
"bootstrap": "^5.3.8",
"brand-colors": "^2.1.1",
"chart.js": "^4.5.1",
"dayjs": "^1.11.19",
"jsvectormap": "^1.7.0",
"load-google-maps-api": "^2.0.2",
"masonry-layout": "^4.2.2",
"perfect-scrollbar": "^1.5.6",
"skycons": "^1.0.0"
}
}
================================================
FILE: src/404.html
================================================
404
404
Oops Page Not Found
The page you are looking for does not exist or has been moved.
================================================
FILE: src/500.html
================================================
500
500
Internal server error
Something goes wrong with our servers, please try again later.
================================================
FILE: src/assets/scripts/app.js
================================================
/**
* Modern Adminator Application
* Main application entry point with enhanced mobile support
*
* @module app
* @version 2.9.0
*/
// Note: Bootstrap 5 CSS is still available via SCSS imports
// Bootstrap JS components removed to eliminate jQuery dependency
import { DOM } from './utils/dom';
import DateUtils from './utils/date';
import ThemeManager from './utils/theme';
import Events from './utils/events';
import Performance from './utils/performance';
import Logger from './utils/logger';
import Sidebar from './components/Sidebar';
import ChartComponent from './components/Chart';
// Import styles
import '../styles/index.scss';
// Import other modules that don't need immediate modernization
import './fullcalendar';
import './masonry';
import './popover';
import './scrollbar';
import './search';
import './skycons';
import './vectorMaps';
import './chat';
import './email';
import './googleMaps';
import './ui';
class AdminatorApp {
constructor() {
this.components = new Map();
this.isInitialized = false;
this.themeManager = ThemeManager;
this.cleanupFunctions = [];
// Initialize when DOM is ready
DOM.ready(() => {
this.init();
});
}
/**
* Initialize the application
*/
init() {
if (this.isInitialized) return;
Logger.time('Adminator Init');
try {
// Initialize core components
this.initSidebar();
this.initCharts();
this.initDataTables();
this.initDatePickers();
this.initTheme();
this.initMobileEnhancements();
// Setup global event listeners using event delegation
this.setupGlobalEvents();
this.isInitialized = true;
Logger.timeEnd('Adminator Init');
Logger.info('Application initialized successfully');
// Dispatch custom event for other scripts
Events.emit(window, 'adminator:ready', { app: this });
} catch (error) {
Logger.error('Error initializing Adminator App', error);
}
}
/**
* Initialize Sidebar component
*/
initSidebar() {
if (DOM.exists('.sidebar')) {
const sidebar = new Sidebar();
this.components.set('sidebar', sidebar);
}
}
/**
* Initialize Chart components
*/
initCharts() {
// Check if we have any chart elements
const hasCharts = DOM.exists('#sparklinedash') ||
DOM.exists('.sparkline') ||
DOM.exists('.sparkbar') ||
DOM.exists('.sparktri') ||
DOM.exists('.sparkdisc') ||
DOM.exists('.sparkbull') ||
DOM.exists('.sparkbox') ||
DOM.exists('.easy-pie-chart') ||
DOM.exists('#line-chart') ||
DOM.exists('#area-chart') ||
DOM.exists('#scatter-chart') ||
DOM.exists('#bar-chart');
if (hasCharts) {
const charts = new ChartComponent();
this.components.set('charts', charts);
}
}
/**
* Initialize DataTables (modern approach)
*/
initDataTables() {
const dataTableElement = DOM.select('#dataTable');
if (dataTableElement) {
// For now, use a lightweight approach
// In future iterations, we can replace with a modern table library
this.initBasicDataTable(dataTableElement);
}
}
/**
* Basic DataTable implementation (placeholder for full modernization)
*/
initBasicDataTable(table) {
// Add basic sorting functionality
const headers = DOM.selectAll('th', table);
headers.forEach(header => {
if (header.textContent.trim()) {
header.style.cursor = 'pointer';
header.style.userSelect = 'none';
DOM.on(header, 'click', () => {
// Basic sort functionality can be added here
// For now, we'll keep the existing DataTables library
});
}
});
}
/**
* Initialize Date Pickers (modern approach with Day.js)
*/
initDatePickers() {
const startDatePickers = DOM.selectAll('.start-date');
const endDatePickers = DOM.selectAll('.end-date');
[...startDatePickers, ...endDatePickers].forEach(picker => {
// Convert to HTML5 date input for better UX
if (picker.type !== 'date') {
picker.type = 'date';
picker.classList.add('form-control');
// Clear the placeholder since HTML5 date inputs don't need it
picker.removeAttribute('placeholder');
// Set default value to today if no value is set
if (!picker.value) {
picker.value = DateUtils.form.toInputValue(DateUtils.now());
}
// Make sure the input is clickable and focusable
picker.style.pointerEvents = 'auto';
picker.style.cursor = 'pointer';
// Ensure proper styling for HTML5 date input
picker.style.minHeight = '38px';
picker.style.lineHeight = '1.5';
// Date picker converted to HTML5 with Day.js support
}
});
// Add enhanced interaction - handle both field and icon clicks
[...startDatePickers, ...endDatePickers].forEach(picker => {
// Handle direct field clicks
DOM.on(picker, 'click', (event) => {
event.target.focus();
// For mobile browsers, trigger the date picker
if (event.target.showPicker && typeof event.target.showPicker === 'function') {
try {
event.target.showPicker();
} catch {
// Fallback if showPicker is not supported
}
}
});
// Handle calendar icon clicks (find the icon in the input group)
const inputGroup = picker.closest('.input-group');
if (inputGroup) {
const calendarIcon = inputGroup.querySelector('.input-group-text i.ti-calendar');
if (calendarIcon) {
DOM.on(calendarIcon, 'click', (event) => {
event.preventDefault();
event.stopPropagation();
picker.focus();
if (picker.showPicker && typeof picker.showPicker === 'function') {
try {
picker.showPicker();
} catch {
// Date picker opened via icon click
}
}
});
}
}
});
}
/**
* Initialize theme system with toggle
*/
initTheme() {
// Initializing theme system
// Initialize theme system first
this.themeManager.init();
// Inject theme toggle if missing - with retry mechanism
setTimeout(() => {
const navRight = DOM.select('.nav-right');
// Check for nav-right and theme-toggle existence
if (navRight && !DOM.exists('#theme-toggle')) {
const li = document.createElement('li');
li.className = 'theme-toggle d-flex ai-c';
const currentTheme = this.themeManager.current();
const isDark = currentTheme === 'dark';
li.innerHTML = `
Light
Dark
`;
// Insert before user dropdown (last item) - safer approach
const lastItem = navRight.querySelector('li:last-child');
if (lastItem && lastItem.parentNode === navRight) {
navRight.insertBefore(li, lastItem);
// Theme toggle inserted before last item
} else {
navRight.appendChild(li);
// Theme toggle appended to nav-right (safer approach)
}
// Add toggle functionality
const toggle = DOM.select('#theme-toggle');
if (toggle) {
DOM.on(toggle, 'change', () => {
const newTheme = toggle.checked ? 'dark' : 'light';
toggle.setAttribute('aria-checked', toggle.checked ? 'true' : 'false');
this.themeManager.apply(newTheme);
});
// Listen for theme changes from other sources
window.addEventListener('adminator:themeChanged', (event) => {
const isDark = event.detail.theme === 'dark';
toggle.checked = isDark;
toggle.setAttribute('aria-checked', isDark ? 'true' : 'false');
// Update charts when theme changes
const charts = this.components.get('charts');
if (charts) charts.redrawCharts();
});
}
} else {
// No nav-right found or theme-toggle already exists
}
}, 100); // Wait 100ms for DOM to be fully ready
}
/**
* Initialize mobile-specific enhancements
*/
initMobileEnhancements() {
// Initializing mobile enhancements
this.enhanceMobileDropdowns();
this.enhanceMobileSearch();
// Prevent horizontal scroll on mobile
if (this.isMobile()) {
document.body.style.overflowX = 'hidden';
}
}
/**
* Setup global event listeners using event delegation for performance
*/
setupGlobalEvents() {
// Use event delegation for dropdown clicks (single listener instead of many)
const dropdownCleanup = Events.delegate(
document,
'click',
'.nav-right .dropdown-toggle',
(e, toggle) => this.handleDropdownClick(e, toggle)
);
this.cleanupFunctions.push(dropdownCleanup);
// Global click handler for closing dropdowns/search
const globalClickCleanup = Events.on(document, 'click', (event) => {
this.handleGlobalClick(event);
});
this.cleanupFunctions.push(globalClickCleanup);
// Window resize handler with debouncing using Events utility
const debouncedResize = Events.debounce(() => this.handleResize(), 250);
const resizeCleanup = Events.on(window, 'resize', debouncedResize);
this.cleanupFunctions.push(resizeCleanup);
// Escape key handler using delegation
const escapeCleanup = Events.on(document, 'keydown', (e) => {
if (e.key === 'Escape') {
this.closeAllOverlays();
}
});
this.cleanupFunctions.push(escapeCleanup);
Logger.debug('Global event listeners set up with delegation');
}
/**
* Handle dropdown toggle clicks
* @param {Event} e - Click event
* @param {Element} toggle - The clicked toggle element
*/
handleDropdownClick(e, toggle) {
if (!this.isMobile()) return;
e.preventDefault();
e.stopPropagation();
const dropdown = toggle.closest('.dropdown');
const menu = dropdown?.querySelector('.dropdown-menu');
if (!dropdown || !menu) return;
// Close search if open
this.closeSearch();
// Close other dropdowns
DOM.selectAll('.nav-right .dropdown').forEach(d => {
if (d !== dropdown) {
d.classList.remove('show');
d.querySelector('.dropdown-menu')?.classList.remove('show');
}
});
// Toggle current dropdown
const isOpen = dropdown.classList.contains('show');
dropdown.classList.toggle('show', !isOpen);
menu.classList.toggle('show', !isOpen);
document.body.style.overflow = isOpen ? '' : 'hidden';
document.body.classList.toggle('mobile-menu-open', !isOpen);
}
/**
* Close all overlays (dropdowns, search)
*/
closeAllOverlays() {
// Close dropdowns
DOM.selectAll('.nav-right .dropdown').forEach(dropdown => {
dropdown.classList.remove('show');
dropdown.querySelector('.dropdown-menu')?.classList.remove('show');
});
// Close search
this.closeSearch();
document.body.style.overflow = '';
document.body.classList.remove('mobile-menu-open');
}
/**
* Close the search overlay
*/
closeSearch() {
const searchBox = DOM.select('.search-box');
const searchInput = DOM.select('.search-input');
if (searchBox && searchInput) {
searchBox.classList.remove('active');
searchInput.classList.remove('active');
document.body.classList.remove('search-open');
// Reset icon
const searchIcon = searchBox.querySelector('i');
if (searchIcon) {
searchIcon.className = 'ti-search';
}
// Clear input
const field = searchInput.querySelector('input');
if (field) {
field.value = '';
field.blur();
}
}
}
/**
* Handle window resize events
*/
handleResize() {
// Window resized, updating mobile features
// Close all mobile-specific overlays when switching to desktop
if (!this.isMobile()) {
document.body.style.overflow = '';
document.body.style.overflowX = '';
// Close dropdowns
const dropdowns = DOM.selectAll('.nav-right .dropdown');
dropdowns.forEach(dropdown => {
dropdown.classList.remove('show');
const menu = dropdown.querySelector('.dropdown-menu');
if (menu) menu.classList.remove('show');
});
// Close search
const searchBox = DOM.select('.search-box');
const searchInput = DOM.select('.search-input');
if (searchBox && searchInput) {
searchBox.classList.remove('active');
searchInput.classList.remove('active');
}
} else {
// Re-enable mobile overflow protection
document.body.style.overflowX = 'hidden';
}
// Re-apply mobile enhancements
this.enhanceMobileDropdowns();
this.enhanceMobileSearch();
}
/**
* Handle global click events
*/
handleGlobalClick(event) {
// Close mobile dropdowns when clicking outside
if (!event.target.closest('.dropdown')) {
const dropdowns = DOM.selectAll('.nav-right .dropdown');
dropdowns.forEach(dropdown => {
dropdown.classList.remove('show');
const menu = dropdown.querySelector('.dropdown-menu');
if (menu) menu.classList.remove('show');
});
document.body.style.overflow = '';
}
// Close search when clicking outside
if (!event.target.closest('.search-box') && !event.target.closest('.search-input')) {
const searchBox = DOM.select('.search-box');
const searchInput = DOM.select('.search-input');
if (searchBox && searchInput) {
searchBox.classList.remove('active');
searchInput.classList.remove('active');
document.body.style.overflow = '';
document.body.classList.remove('mobile-menu-open');
}
}
}
/**
* Check if we're on a mobile device
*/
isMobile() {
return window.innerWidth <= 768;
}
/**
* Enhanced mobile dropdown handling with improved email layout
*/
enhanceMobileDropdowns() {
if (!this.isMobile()) return;
const dropdowns = DOM.selectAll('.nav-right .dropdown');
dropdowns.forEach(dropdown => {
const toggle = dropdown.querySelector('.dropdown-toggle');
const menu = dropdown.querySelector('.dropdown-menu');
if (toggle && menu) {
// Remove existing listeners to prevent duplicates
const newToggle = toggle.cloneNode(true);
toggle.replaceWith(newToggle);
// Add click functionality for mobile dropdowns
DOM.on(newToggle, 'click', (e) => {
e.preventDefault();
e.stopPropagation();
// Close search if open
const searchBox = DOM.select('.search-box');
const searchInput = DOM.select('.search-input');
if (searchBox && searchInput) {
searchBox.classList.remove('active');
searchInput.classList.remove('active');
}
// Close other dropdowns first
dropdowns.forEach(otherDropdown => {
if (otherDropdown !== dropdown) {
otherDropdown.classList.remove('show');
const otherMenu = otherDropdown.querySelector('.dropdown-menu');
if (otherMenu) otherMenu.classList.remove('show');
}
});
// Toggle current dropdown
const isOpen = dropdown.classList.contains('show');
if (isOpen) {
dropdown.classList.remove('show');
menu.classList.remove('show');
document.body.style.overflow = '';
document.body.classList.remove('mobile-menu-open');
} else {
dropdown.classList.add('show');
menu.classList.add('show');
document.body.style.overflow = 'hidden';
document.body.classList.add('mobile-menu-open');
}
});
// Enhanced mobile close button functionality
DOM.on(menu, 'click', (e) => {
// Check if clicked on the close area (::before pseudo-element area)
const rect = menu.getBoundingClientRect();
const clickY = e.clientY - rect.top;
// If clicked in top 50px (close button area)
if (clickY <= 50) {
dropdown.classList.remove('show');
menu.classList.remove('show');
document.body.style.overflow = '';
document.body.classList.remove('mobile-menu-open');
e.preventDefault();
e.stopPropagation();
}
});
}
});
// Close dropdowns on escape key
DOM.on(document, 'keydown', (e) => {
if (e.key === 'Escape') {
dropdowns.forEach(dropdown => {
dropdown.classList.remove('show');
const menu = dropdown.querySelector('.dropdown-menu');
if (menu) menu.classList.remove('show');
});
document.body.style.overflow = '';
document.body.classList.remove('mobile-menu-open');
}
});
}
/**
* Enhanced mobile search handling - Full-width search bar
*/
enhanceMobileSearch() {
const searchBox = DOM.select('.search-box');
const searchInput = DOM.select('.search-input');
if (searchBox && searchInput) {
const searchToggle = searchBox.querySelector('a');
const searchField = searchInput.querySelector('input');
if (searchToggle && searchField) {
// Setting up full-width search functionality
// Remove existing listeners to prevent duplication
const newSearchToggle = searchToggle.cloneNode(true);
searchToggle.replaceWith(newSearchToggle);
DOM.on(newSearchToggle, 'click', (e) => {
e.preventDefault();
e.stopPropagation();
// Full-width search toggle clicked
// Close any open dropdowns first
const dropdowns = DOM.selectAll('.nav-right .dropdown');
dropdowns.forEach(dropdown => {
dropdown.classList.remove('show');
const menu = dropdown.querySelector('.dropdown-menu');
if (menu) menu.classList.remove('show');
});
// Toggle search state
const isActive = searchInput.classList.contains('active');
const searchIcon = newSearchToggle.querySelector('i');
if (isActive) {
// Close search
searchInput.classList.remove('active');
document.body.classList.remove('search-open');
// Change icon back to search
if (searchIcon) {
searchIcon.className = 'ti-search';
}
// Clear input
if (searchField) {
searchField.value = '';
searchField.blur();
}
// Full-width search closed
} else {
// Open search
searchInput.classList.add('active');
document.body.classList.add('search-open');
// Change icon to close
if (searchIcon) {
searchIcon.className = 'ti-close';
}
// Focus the input after a short delay
setTimeout(() => {
if (searchField) {
searchField.focus();
// Search field focused
}
}, 100);
// Full-width search opened
}
});
// Close search on escape
DOM.on(document, 'keydown', (e) => {
if (e.key === 'Escape' && searchInput.classList.contains('active')) {
searchInput.classList.remove('active');
document.body.classList.remove('search-open');
// Reset icon
const searchIcon = newSearchToggle.querySelector('i');
if (searchIcon) {
searchIcon.className = 'ti-search';
}
// Clear input
if (searchField) {
searchField.value = '';
searchField.blur();
}
// Full-width search closed via escape
}
});
// Handle search input
DOM.on(searchField, 'keypress', (e) => {
if (e.key === 'Enter') {
e.preventDefault();
const query = searchField.value.trim();
if (query) {
// Search query submitted
// Implement your search logic here
// For demo, close search after "searching"
searchInput.classList.remove('active');
document.body.classList.remove('search-open');
const searchIcon = newSearchToggle.querySelector('i');
if (searchIcon) {
searchIcon.className = 'ti-search';
}
searchField.value = '';
searchField.blur();
}
}
});
// Full-width search functionality initialized
}
}
}
/**
* Get a component by name
*/
getComponent(name) {
return this.components.get(name);
}
/**
* Check if app is ready
*/
isReady() {
return this.isInitialized;
}
/**
* Destroy the application and clean up all resources
*/
destroy() {
Logger.info('Destroying Adminator App');
// Clean up all event listeners
this.cleanupFunctions.forEach(cleanup => {
if (typeof cleanup === 'function') {
cleanup();
}
});
this.cleanupFunctions = [];
// Destroy all components
this.components.forEach((component, name) => {
if (typeof component.destroy === 'function') {
component.destroy();
Logger.debug(`Component destroyed: ${name}`);
}
});
this.components.clear();
// Cleanup performance observers
Performance.cleanup();
this.isInitialized = false;
Logger.info('Adminator App destroyed');
}
/**
* Refresh/reinitialize the application
*/
refresh() {
Logger.info('Refreshing Adminator App');
if (this.isInitialized) {
this.destroy();
}
setTimeout(() => {
this.init();
}, 100);
}
}
// Initialize the application
const app = new AdminatorApp();
// Make app globally available for debugging
window.AdminatorApp = app;
// Export for module usage
export default app;
================================================
FILE: src/assets/scripts/charts/chartJS/index.js
================================================
import Chart from 'chart.js/auto';
import { COLORS } from '../../constants/colors';
export default (function () {
// ------------------------------------------------------
// @Line Charts
// ------------------------------------------------------
const lineChartBox = document.getElementById('line-chart');
if (lineChartBox) {
const lineCtx = lineChartBox.getContext('2d');
lineChartBox.height = 80;
new Chart(lineCtx, {
type: 'line',
data: {
labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'],
datasets: [{
label : 'Series A',
backgroundColor : 'rgba(237, 231, 246, 0.5)',
borderColor : COLORS['deep-purple-500'],
pointBackgroundColor : COLORS['deep-purple-700'],
borderWidth : 2,
data : [60, 50, 70, 60, 50, 70, 60],
}, {
label : 'Series B',
backgroundColor : 'rgba(232, 245, 233, 0.5)',
borderColor : COLORS['blue-500'],
pointBackgroundColor : COLORS['blue-700'],
borderWidth : 2,
data : [70, 75, 85, 70, 75, 85, 70],
}],
},
options: {
legend: {
display: false,
},
},
});
}
// ------------------------------------------------------
// @Bar Charts
// ------------------------------------------------------
const barChartBox = document.getElementById('bar-chart');
if (barChartBox) {
const barCtx = barChartBox.getContext('2d');
new Chart(barCtx, {
type: 'bar',
data: {
labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'],
datasets: [{
label : 'Dataset 1',
backgroundColor : COLORS['deep-purple-500'],
borderColor : COLORS['deep-purple-800'],
borderWidth : 1,
data : [10, 50, 20, 40, 60, 30, 70],
}, {
label : 'Dataset 2',
backgroundColor : COLORS['light-blue-500'],
borderColor : COLORS['light-blue-800'],
borderWidth : 1,
data : [10, 50, 20, 40, 60, 30, 70],
}],
},
options: {
responsive: true,
legend: {
position: 'bottom',
},
},
});
}
// ------------------------------------------------------
// @Area Charts
// ------------------------------------------------------
const areaChartBox = document.getElementById('area-chart');
if (areaChartBox) {
const areaCtx = areaChartBox.getContext('2d');
new Chart(areaCtx, {
type: 'line',
data: {
labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'],
datasets: [{
backgroundColor : 'rgba(3, 169, 244, 0.5)',
borderColor : COLORS['light-blue-800'],
data : [10, 50, 20, 40, 60, 30, 70],
label : 'Dataset',
fill : 'start',
}],
},
});
}
// ------------------------------------------------------
// @Scatter Charts
// ------------------------------------------------------
const scatterChartBox = document.getElementById('scatter-chart');
if (scatterChartBox) {
const scatterCtx = scatterChartBox.getContext('2d');
new Chart(scatterCtx, {
type: 'scatter',
data: {
datasets: [{
label : 'My First dataset',
borderColor : COLORS['red-500'],
backgroundColor : COLORS['red-500'],
data: [
{ x: 10, y: 20 },
{ x: 30, y: 40 },
{ x: 50, y: 60 },
{ x: 70, y: 80 },
{ x: 90, y: 100 },
{ x: 110, y: 120 },
{ x: 130, y: 140 },
],
}, {
label : 'My Second dataset',
borderColor : COLORS['green-500'],
backgroundColor : COLORS['green-500'],
data: [
{ x: 150, y: 160 },
{ x: 170, y: 180 },
{ x: 190, y: 200 },
{ x: 210, y: 220 },
{ x: 230, y: 240 },
{ x: 250, y: 260 },
{ x: 270, y: 280 },
],
}],
},
});
}
}())
================================================
FILE: src/assets/scripts/charts/easyPieChart/index.js
================================================
import Theme from '../../utils/theme.js';
export default (function () {
// Vanilla JS Pie Chart implementation using SVG
class VanillaPieChart {
constructor(element, options = {}) {
this.element = element;
this.options = {
size: 110,
lineWidth: 3,
lineCap: 'round',
trackColor: '#f2f2f2',
barColor: '#ef1e25',
scaleColor: false,
animate: 1000,
onStep: null,
...options,
};
this.percentage = parseInt(element.dataset.percent || 0);
this.init();
}
init() {
this.createSVG();
this.animate();
}
createSVG() {
const size = this.options.size;
const lineWidth = this.options.lineWidth;
const radius = (size - lineWidth) / 2;
const circumference = 2 * Math.PI * radius;
// Create SVG element
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svg.setAttribute('width', size);
svg.setAttribute('height', size);
svg.style.transform = 'rotate(-90deg)';
// Create track (background circle)
const track = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
track.setAttribute('cx', size / 2);
track.setAttribute('cy', size / 2);
track.setAttribute('r', radius);
track.setAttribute('fill', 'none');
track.setAttribute('stroke', this.options.trackColor);
track.setAttribute('stroke-width', lineWidth);
// Create bar (progress circle)
const bar = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
bar.setAttribute('cx', size / 2);
bar.setAttribute('cy', size / 2);
bar.setAttribute('r', radius);
bar.setAttribute('fill', 'none');
bar.setAttribute('stroke', this.options.barColor);
bar.setAttribute('stroke-width', lineWidth);
bar.setAttribute('stroke-linecap', this.options.lineCap);
bar.setAttribute('stroke-dasharray', circumference);
bar.setAttribute('stroke-dashoffset', circumference);
// Add elements to SVG
svg.appendChild(track);
svg.appendChild(bar);
// Clear element and add SVG
this.element.innerHTML = '';
this.element.appendChild(svg);
// Add percentage text
const textElement = document.createElement('div');
textElement.style.position = 'absolute';
textElement.style.top = '50%';
textElement.style.left = '50%';
textElement.style.transform = 'translate(-50%, -50%)';
textElement.style.fontSize = '14px';
textElement.style.fontWeight = 'bold';
textElement.style.color = Theme.getCSSVar('--c-text-base') || '#333';
textElement.textContent = '0%';
this.element.style.position = 'relative';
this.element.appendChild(textElement);
// Store references
this.svg = svg;
this.bar = bar;
this.textElement = textElement;
this.circumference = circumference;
}
animate() {
const targetOffset = this.circumference - (this.percentage / 100) * this.circumference;
const duration = this.options.animate;
const startTime = Date.now();
const startOffset = this.circumference;
const animateStep = () => {
const elapsed = Date.now() - startTime;
const progress = Math.min(elapsed / duration, 1);
// Easing function (easeOutCubic)
const easeProgress = 1 - Math.pow(1 - progress, 3);
const currentOffset = startOffset - (startOffset - targetOffset) * easeProgress;
const currentPercent = ((this.circumference - currentOffset) / this.circumference) * 100;
this.bar.setAttribute('stroke-dashoffset', currentOffset);
this.textElement.textContent = `${Math.round(currentPercent)}%`;
// Call onStep callback if provided
if (this.options.onStep) {
this.options.onStep.call(this, 0, this.percentage, currentPercent);
}
if (progress < 1) {
requestAnimationFrame(animateStep);
}
};
requestAnimationFrame(animateStep);
}
update(percentage) {
this.percentage = percentage;
this.animate();
}
destroy() {
if (this.element) {
this.element.innerHTML = '';
}
}
}
// Initialize all pie charts
const initializePieCharts = () => {
const pieChartElements = document.querySelectorAll('.easy-pie-chart');
pieChartElements.forEach(element => {
// Skip if already initialized
if (element.pieChartInstance) {
element.pieChartInstance.destroy();
}
// Get theme colors
const isDark = Theme.current() === 'dark';
const barColor = element.dataset.barColor || (isDark ? '#4f46e5' : '#ef4444');
const trackColor = element.dataset.trackColor || (isDark ? '#374151' : '#f3f4f6');
// Create pie chart instance
const pieChart = new VanillaPieChart(element, {
size: parseInt(element.dataset.size || 110),
lineWidth: parseInt(element.dataset.lineWidth || 3),
barColor,
trackColor,
animate: parseInt(element.dataset.animate || 1000),
onStep(from, to, percent) {
// Update the percentage display
const textElement = this.element.querySelector('div');
if (textElement) {
textElement.innerHTML = `${Math.round(percent)}%`;
}
},
});
// Store instance for cleanup
element.pieChartInstance = pieChart;
});
};
// Initialize on load
initializePieCharts();
// Reinitialize on theme change
window.addEventListener('adminator:themeChanged', () => {
setTimeout(initializePieCharts, 100);
});
// Reinitialize on window resize
window.addEventListener('resize', () => {
setTimeout(initializePieCharts, 100);
});
// Cleanup on page unload
window.addEventListener('beforeunload', () => {
const pieChartElements = document.querySelectorAll('.easy-pie-chart');
pieChartElements.forEach(element => {
if (element.pieChartInstance) {
element.pieChartInstance.destroy();
}
});
});
// Return public API
return {
init: initializePieCharts,
VanillaPieChart,
};
}());
================================================
FILE: src/assets/scripts/charts/index.js
================================================
import './chartJS';
import './easyPieChart';
import './sparkline';
================================================
FILE: src/assets/scripts/charts/sparkline/index.js
================================================
import { Chart, registerables } from 'chart.js';
import { COLORS } from '../../constants/colors';
import Theme from '../../utils/theme.js';
import { Events } from '../../utils';
// Register Chart.js components
Chart.register(...registerables);
export default (function () {
// Store chart instances for cleanup
let chartInstances = [];
// ------------------------------------------------------
// @Sparkline Chart Creation Helpers
// ------------------------------------------------------
const createSparklineChart = (elementId, data, color, type = 'bar') => {
const element = document.getElementById(elementId);
if (!element) return null;
// Clear existing chart
const existingChart = chartInstances.find(chart => chart.canvas.id === elementId);
if (existingChart) {
existingChart.destroy();
chartInstances = chartInstances.filter(chart => chart.canvas.id !== elementId);
}
// Create canvas if it doesn't exist
let canvas = element.querySelector('canvas');
if (!canvas) {
canvas = document.createElement('canvas');
canvas.id = `${elementId }-canvas`;
element.appendChild(canvas);
}
// Set canvas size
canvas.width = element.offsetWidth || 100;
canvas.height = 20;
const ctx = canvas.getContext('2d');
const chartConfig = {
type,
data: {
labels: data.map((_, index) => index),
datasets: [{
data,
backgroundColor: color,
borderColor: color,
borderWidth: type === 'line' ? 2 : 0,
barThickness: 3,
categoryPercentage: 1.0,
barPercentage: 0.8,
fill: false,
pointRadius: 0,
pointHoverRadius: 0,
}],
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: false,
},
tooltip: {
enabled: false,
},
},
scales: {
x: {
display: false,
grid: {
display: false,
},
},
y: {
display: false,
grid: {
display: false,
},
},
},
elements: {
bar: {
borderRadius: 0,
},
line: {
tension: 0.1,
},
},
layout: {
padding: 0,
},
},
};
const chart = new Chart(ctx, chartConfig);
chartInstances.push(chart);
return chart;
};
const createSparklineForElements = (selector, data, color, type = 'bar') => {
const elements = document.querySelectorAll(selector);
elements.forEach((element, index) => {
const elementId = element.id || `sparkline-${selector.replace(/[^a-zA-Z0-9]/g, '')}-${index}`;
if (!element.id) element.id = elementId;
createSparklineChart(elementId, data, color, type);
});
};
// ------------------------------------------------------
// @Dashboard Sparklines
// ------------------------------------------------------
const drawSparklines = () => {
const sparkColors = Theme.getSparklineColors();
const data = [0, 5, 6, 10, 9, 12, 4, 9];
// Dashboard sparklines
createSparklineChart('sparklinedash', data, sparkColors.success);
createSparklineChart('sparklinedash2', data, sparkColors.purple);
createSparklineChart('sparklinedash3', data, sparkColors.info);
createSparklineChart('sparklinedash4', data, sparkColors.danger);
};
// ------------------------------------------------------
// @Other Sparklines
// ------------------------------------------------------
const drawOtherSparklines = () => {
const sparkColors = Theme.getSparklineColors();
// Line sparklines
createSparklineChart('sparkline', [5, 6, 7, 9, 9, 5, 3, 2, 2, 4, 6, 7], COLORS['red-500'], 'line');
// Composite bar - simplified implementation
createSparklineChart('compositebar', [4, 1, 5, 7, 9, 9, 8, 7, 6, 6, 4, 7, 8, 4, 3, 2, 2, 5, 6, 7], sparkColors.light);
// Normal line
createSparklineChart('normalline', [5, 6, 7, 9, 9, 5, 3, 2, 2, 4, 6, 7], sparkColors.info, 'line');
// Various sparkline types for elements with classes
const values = [5, 4, 5, -2, 0, 3, -5, 6, 7, 9, 9, 5, -3, -2, 2, -4];
const valuesAlt = [1, 1, 0, 1, -1, -1, 1, -1, 0, 0, 1, 1];
// Line sparklines
createSparklineForElements('.sparkline', values, COLORS['red-500'], 'line');
// Bar sparklines
createSparklineForElements('.sparkbar', values, COLORS['deep-purple-500'], 'bar');
// Tristate sparklines (simplified as bar charts)
createSparklineForElements('.sparktri', valuesAlt, COLORS['light-blue-500'], 'bar');
createSparklineForElements('.sparktristate', valuesAlt, sparkColors.info, 'bar');
createSparklineForElements('.sparktristatecols', valuesAlt, '#fa7', 'bar');
// Discrete sparklines (as line charts)
createSparklineForElements('.sparkdisc', values, '#9f0', 'line');
// Bullet sparklines (simplified as bar charts)
createSparklineForElements('.sparkbull', values, COLORS['amber-500'], 'bar');
// Box sparklines (simplified as bar charts)
createSparklineForElements('.sparkbox', values, '#9f0', 'bar');
};
// ------------------------------------------------------
// @Initialization
// ------------------------------------------------------
const initializeSparklines = () => {
drawSparklines();
drawOtherSparklines();
};
// Initial draw
initializeSparklines();
// Redraw sparklines on window resize
window.addEventListener('resize', Events.debounce(initializeSparklines, 150));
// Listen for theme changes
window.addEventListener('adminator:themeChanged', Events.debounce(initializeSparklines, 150));
// Cleanup function for chart instances
window.addEventListener('beforeunload', () => {
chartInstances.forEach(chart => {
if (chart && typeof chart.destroy === 'function') {
chart.destroy();
}
});
chartInstances = [];
});
// Export for external access
return {
redraw: initializeSparklines,
destroy: () => {
chartInstances.forEach(chart => {
if (chart && typeof chart.destroy === 'function') {
chart.destroy();
}
});
chartInstances = [];
},
};
}());
================================================
FILE: src/assets/scripts/chat/index.js
================================================
export default (function () {
const chatSidebarToggle = document.getElementById('chat-sidebar-toggle');
const chatSidebar = document.getElementById('chat-sidebar');
if (chatSidebarToggle && chatSidebar) {
chatSidebarToggle.addEventListener('click', e => {
chatSidebar.classList.toggle('open');
e.preventDefault();
});
}
}())
================================================
FILE: src/assets/scripts/components/Chart.js
================================================
/**
* Modern Chart Component
* Replaces jQuery Sparkline with Chart.js
*/
import { Chart, registerables } from 'chart.js';
import { COLORS } from '../constants/colors';
// Register Chart.js components
Chart.register(...registerables);
class ChartComponent {
constructor() {
this.charts = new Map(); // Store chart instances
this.debounceTimer = null;
this.init();
}
init() {
// Only disable resizing for small sparkline charts
this.createSparklines();
this.createOtherCharts();
this.setupResizeHandler();
}
/**
* Create sparklines (only for dashboard page)
*/
createSparklines() {
// Only create sparklines if we're on a page that has them
const sparklineExists = document.getElementById('sparklinedash');
if (!sparklineExists) {
return;
}
const sparklineConfigs = [
{
id: 'sparklinedash',
data: [0, 5, 6, 10, 9, 12, 4, 9],
color: '#4caf50',
},
{
id: 'sparklinedash2',
data: [0, 5, 6, 10, 9, 12, 4, 9],
color: '#9675ce',
},
{
id: 'sparklinedash3',
data: [0, 5, 6, 10, 9, 12, 4, 9],
color: '#03a9f3',
},
{
id: 'sparklinedash4',
data: [0, 5, 6, 10, 9, 12, 4, 9],
color: '#f96262',
},
];
sparklineConfigs.forEach(config => {
// Only create if the target element exists
if (document.getElementById(config.id)) {
this.createSparklineChart(config);
}
});
}
/**
* Create sparkline chart from configuration
*/
createSparklineChart({ id, data, color }) {
let canvas = document.getElementById(id);
// Only proceed if we have a valid target element
if (!canvas) {
return;
}
// If element exists but isn't a canvas, replace it with canvas
if (canvas.tagName !== 'CANVAS') {
const parent = canvas.parentNode;
if (!parent) {
return;
}
// Create new canvas element
const newCanvas = document.createElement('canvas');
newCanvas.id = id;
newCanvas.width = 100;
newCanvas.height = 20;
newCanvas.style.width = '100px';
newCanvas.style.height = '20px';
// Replace the span with canvas
parent.replaceChild(newCanvas, canvas);
canvas = newCanvas;
} else {
// Set canvas dimensions to match original sparkline
canvas.width = 100;
canvas.height = 20;
canvas.style.width = '100px';
canvas.style.height = '20px';
}
const ctx = canvas.getContext('2d');
const chart = new Chart(ctx, {
type: 'bar',
data: {
labels: data.map((_, i) => i),
datasets: [{
data,
backgroundColor: color,
borderColor: color,
borderWidth: 0,
barPercentage: 0.6,
categoryPercentage: 0.8,
}],
},
options: {
responsive: false,
maintainAspectRatio: false,
animation: false,
events: [],
onResize: null,
scales: {
x: {
display: false,
},
y: {
display: false,
},
},
plugins: {
legend: {
display: false,
},
tooltip: {
enabled: false,
},
},
elements: {
bar: {
borderRadius: 1,
},
},
},
});
this.charts.set(id, chart);
}
/**
* Create other chart types (only if they exist on the page)
*/
createOtherCharts() {
// Determine if we're on the dashboard or charts page
const isChartsPage = document.getElementById('area-chart') !== null;
const isDashboard = !isChartsPage && document.getElementById('line-chart') !== null;
// Create Monthly Stats chart with enhanced dual-line data (dashboard only)
if (isDashboard) {
this.createMonthlyStatsChart();
}
// Charts page specific charts (only on charts page)
if (isChartsPage) {
this.createChartsPageCharts();
}
// Only create charts if their target elements exist
if (document.getElementById('sparkline')) {
this.createLineChart('sparkline', [5, 6, 7, 9, 9, 5, 3, 2, 2, 4, 6, 7]);
}
if (document.getElementById('compositebar')) {
this.createCompositeChart('compositebar', [4, 1, 5, 7, 9, 9, 8, 7, 6, 6, 4, 7, 8, 4, 3, 2, 2, 5, 6, 7]);
}
// Regular sparklines with custom colors (only on pages that have them)
this.createCustomSparklines();
// Easy Pie Charts (only if they exist)
this.createEasyPieCharts();
}
/**
* Create enhanced Monthly Stats chart with dual lines and more data
*/
createMonthlyStatsChart() {
const canvas = document.getElementById('line-chart');
if (!canvas) return;
const ctx = canvas.getContext('2d');
// Enhanced data for monthly stats
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
const salesData = [120, 135, 145, 165, 180, 195, 210, 225, 240, 220, 200, 185];
const profitData = [45, 52, 58, 62, 68, 75, 82, 88, 92, 85, 78, 72];
const chart = new Chart(ctx, {
type: 'line',
data: {
labels: months,
datasets: [
{
label: 'Sales ($K)',
data: salesData,
borderColor: '#4caf50',
backgroundColor: 'rgba(76, 175, 80, 0.1)',
borderWidth: 3,
pointRadius: 5,
pointHoverRadius: 7,
pointBackgroundColor: '#4caf50',
pointBorderColor: '#ffffff',
pointBorderWidth: 2,
tension: 0.4,
fill: false,
},
{
label: 'Profit ($K)',
data: profitData,
borderColor: '#2196f3',
backgroundColor: 'rgba(33, 150, 243, 0.1)',
borderWidth: 3,
pointRadius: 5,
pointHoverRadius: 7,
pointBackgroundColor: '#2196f3',
pointBorderColor: '#ffffff',
pointBorderWidth: 2,
tension: 0.4,
fill: false,
},
],
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: true,
position: 'top',
labels: {
padding: 20,
font: {
size: 12,
weight: '600',
},
},
},
tooltip: {
enabled: true,
cornerRadius: 8,
displayColors: true,
intersect: false,
mode: 'index',
callbacks: {
label(context) {
return `${context.dataset.label }: $${ context.parsed.y }K`;
},
},
},
},
scales: {
x: {
grid: {
display: false,
},
ticks: {
font: {
size: 11,
},
},
},
y: {
beginAtZero: true,
grid: {
borderDash: [5, 5],
},
ticks: {
font: {
size: 11,
},
callback(value) {
return `$${ value }K`;
},
},
},
},
interaction: {
intersect: false,
mode: 'index',
},
},
});
this.charts.set('line-chart', chart);
}
/**
* Create line chart (only if target exists)
*/
createLineChart(id, data) {
let canvas = document.getElementById(id);
// Only proceed if target element exists
if (!canvas) {
return;
}
// If element exists but isn't a canvas, replace it with canvas
if (canvas.tagName !== 'CANVAS') {
const parent = canvas.parentNode;
if (!parent) {
return;
}
// Create new canvas element
const newCanvas = document.createElement('canvas');
newCanvas.id = id;
newCanvas.width = 100;
newCanvas.height = 20;
newCanvas.style.width = '100px';
newCanvas.style.height = '20px';
// Replace element with canvas
parent.replaceChild(newCanvas, canvas);
canvas = newCanvas;
} else {
canvas.width = 100;
canvas.height = 20;
canvas.style.width = '100px';
canvas.style.height = '20px';
}
const ctx = canvas.getContext('2d');
const chart = new Chart(ctx, {
type: 'line',
data: {
labels: data.map((_, i) => i),
datasets: [{
data,
borderColor: COLORS['blue-500'],
backgroundColor: 'transparent',
borderWidth: 1,
pointRadius: 0,
tension: 0.4,
}],
},
options: {
responsive: false,
maintainAspectRatio: false,
animation: false,
events: [],
onResize: null,
scales: {
x: { display: false },
y: { display: false },
},
plugins: {
legend: { display: false },
tooltip: { enabled: false },
},
},
});
this.charts.set(id, chart);
}
/**
* Create composite chart (only if target exists)
*/
createCompositeChart(id, data) {
let canvas = document.getElementById(id);
// Only proceed if target element exists
if (!canvas) {
return;
}
// If element exists but isn't a canvas, replace it with canvas
if (canvas.tagName !== 'CANVAS') {
const parent = canvas.parentNode;
if (!parent) {
return;
}
// Create new canvas element
const newCanvas = document.createElement('canvas');
newCanvas.id = id;
newCanvas.width = 100;
newCanvas.height = 20;
newCanvas.style.width = '100px';
newCanvas.style.height = '20px';
// Replace element with canvas
parent.replaceChild(newCanvas, canvas);
canvas = newCanvas;
} else {
canvas.width = 100;
canvas.height = 20;
canvas.style.width = '100px';
canvas.style.height = '20px';
}
const ctx = canvas.getContext('2d');
const chart = new Chart(ctx, {
type: 'bar',
data: {
labels: data.map((_, i) => i),
datasets: [
{
type: 'bar',
data,
backgroundColor: '#aaf',
borderColor: '#aaf',
borderWidth: 0,
},
{
type: 'line',
data,
borderColor: 'red',
backgroundColor: 'transparent',
borderWidth: 1,
pointRadius: 0,
tension: 0.4,
},
],
},
options: {
responsive: false,
maintainAspectRatio: false,
animation: false,
events: [],
onResize: null,
scales: {
x: { display: false },
y: { display: false },
},
plugins: {
legend: { display: false },
tooltip: { enabled: false },
},
},
});
this.charts.set(id, chart);
}
/**
* Create custom sparklines for different elements (only if they exist)
*/
createCustomSparklines() {
const sparklineElements = document.querySelectorAll('.sparkline');
const sparkbarElements = document.querySelectorAll('.sparkbar');
const sparktriElements = document.querySelectorAll('.sparktri');
const sparkdiscElements = document.querySelectorAll('.sparkdisc');
const sparkbullElements = document.querySelectorAll('.sparkbull');
const sparkboxElements = document.querySelectorAll('.sparkbox');
// Only create if we have elements
if (sparklineElements.length === 0 && sparkbarElements.length === 0 &&
sparktriElements.length === 0 && sparkdiscElements.length === 0 &&
sparkbullElements.length === 0 && sparkboxElements.length === 0) {
return;
}
const values = [5, 4, 5, -2, 0, 3, -5, 6, 7, 9, 9, 5, -3, -2, 2, -4];
const valuesAlt = [1, 1, 0, 1, -1, -1, 1, -1, 0, 0, 1, 1];
sparklineElements.forEach((element, index) => {
this.createCustomLineChart(element, values, `sparkline-${index}`);
});
sparkbarElements.forEach((element, index) => {
this.createCustomBarChart(element, values, `sparkbar-${index}`);
});
sparktriElements.forEach((element, index) => {
this.createTristateChart(element, valuesAlt, `sparktri-${index}`);
});
sparkdiscElements.forEach((element, index) => {
this.createDiscreteChart(element, values, `sparkdisc-${index}`);
});
sparkbullElements.forEach((element, index) => {
this.createBulletChart(element, values, `sparkbull-${index}`);
});
sparkboxElements.forEach((element, index) => {
this.createBoxChart(element, values, `sparkbox-${index}`);
});
}
/**
* Create custom line chart for sparkline elements
*/
createCustomLineChart(element, data, id) {
// Create canvas if it doesn't exist
let canvas = element.querySelector('canvas');
if (!canvas) {
canvas = document.createElement('canvas');
canvas.width = 100;
canvas.height = 20;
canvas.style.width = '100px';
canvas.style.height = '20px';
element.appendChild(canvas);
}
const ctx = canvas.getContext('2d');
const chart = new Chart(ctx, {
type: 'line',
data: {
labels: data.map((_, i) => i),
datasets: [{
data,
borderColor: COLORS['red-500'],
backgroundColor: 'transparent',
borderWidth: 2,
pointRadius: 3,
pointBackgroundColor: COLORS['red-500'],
tension: 0.4,
}],
},
options: {
responsive: false,
maintainAspectRatio: false,
animation: false, // Disable animations to prevent resize triggers
events: [], // Disable all events to prevent resize
onResize: null, // Explicitly disable resize callback
scales: {
x: { display: false },
y: { display: false },
},
plugins: {
legend: { display: false },
tooltip: { enabled: false }, // Disable tooltip to prevent events
},
},
});
this.charts.set(id, chart);
}
/**
* Create custom bar chart for sparkbar elements
*/
createCustomBarChart(element, data, id) {
// Create canvas if it doesn't exist
let canvas = element.querySelector('canvas');
if (!canvas) {
canvas = document.createElement('canvas');
canvas.width = 100;
canvas.height = 20;
canvas.style.width = '100px';
canvas.style.height = '20px';
element.appendChild(canvas);
}
const ctx = canvas.getContext('2d');
const chart = new Chart(ctx, {
type: 'bar',
data: {
labels: data.map((_, i) => i),
datasets: [{
data,
backgroundColor: data.map(val => val < 0 ? COLORS['deep-purple-500'] : '#39f'),
borderColor: data.map(val => val < 0 ? COLORS['deep-purple-500'] : '#39f'),
borderWidth: 1,
barPercentage: 0.8,
}],
},
options: {
responsive: false,
maintainAspectRatio: false,
scales: {
x: { display: false },
y: { display: false },
},
plugins: {
legend: { display: false },
tooltip: {
enabled: true,
callbacks: {
label: (context) => `${context.parsed.y}°Celsius`,
},
},
},
},
});
this.charts.set(id, chart);
}
/**
* Setup resize handler for charts
*/
setupResizeHandler() {
// Setup responsive resize for large charts only
window.addEventListener('resize', () => {
this.debounceResize();
});
// Listen for sidebar toggle events
window.addEventListener('sidebar:toggle', () => {
this.debounceResize();
});
}
/**
* Debounced resize handler
*/
debounceResize() {
if (this.debounceTimer) {
clearTimeout(this.debounceTimer);
}
this.debounceTimer = setTimeout(() => {
this.redrawLargeChartsOnly();
}, 150);
}
/**
* Redraw only large charts, not sparklines
*/
redrawLargeChartsOnly() {
const largeChartIds = [
'line-chart', 'area-chart', 'scatter-chart', 'bar-chart',
'doughnut-chart', 'polar-chart', 'radar-chart', 'mixed-chart', 'bubble-chart',
];
largeChartIds.forEach(id => {
const chart = this.charts.get(id);
if (chart && chart.options.responsive) {
chart.resize();
}
});
}
/**
* Redraw all charts (used sparingly)
*/
redrawCharts() {
this.charts.forEach((chart) => {
if (chart.options.responsive) {
chart.resize();
}
});
}
/**
* Update chart data
*/
updateChart(id, newData) {
const chart = this.charts.get(id);
if (chart) {
chart.data.datasets[0].data = newData;
chart.update();
}
}
/**
* Create charts for the charts.html page
*/
createChartsPageCharts() {
// Line Chart
this.createLargeChart('line-chart', 'line', {
labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'],
datasets: [{
label: 'Dataset 1',
data: [65, 59, 80, 81, 56, 55, 40],
borderColor: 'rgb(75, 192, 192)',
backgroundColor: 'rgba(75, 192, 192, 0.2)',
tension: 0.4,
}],
});
// Area Chart
this.createLargeChart('area-chart', 'line', {
labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'],
datasets: [{
label: 'Dataset 1',
data: [65, 59, 80, 81, 56, 55, 40],
borderColor: 'rgb(54, 162, 235)',
backgroundColor: 'rgba(54, 162, 235, 0.4)',
fill: true,
tension: 0.4,
}],
});
// Scatter Chart with more data points
this.createLargeChart('scatter-chart', 'scatter', {
datasets: [{
label: 'Dataset 1',
data: [
{x: -15, y: 8}, {x: -12, y: 12}, {x: -8, y: 3}, {x: -5, y: 15},
{x: -2, y: 7}, {x: 0, y: 10}, {x: 3, y: 18}, {x: 6, y: 5},
{x: 9, y: 22}, {x: 12, y: 8}, {x: 15, y: 14}, {x: 18, y: 19},
{x: -10, y: 0}, {x: 10, y: 5}, {x: 0.5, y: 5.5}, {x: 7, y: 12},
{x: -7, y: 17}, {x: 4, y: 9}, {x: 11, y: 16}, {x: -3, y: 11},
],
backgroundColor: 'rgba(255, 99, 132, 0.7)',
borderColor: 'rgb(255, 99, 132)',
borderWidth: 1,
}, {
label: 'Dataset 2',
data: [
{x: -13, y: 4}, {x: -9, y: 8}, {x: -6, y: 13}, {x: -1, y: 6},
{x: 2, y: 11}, {x: 5, y: 15}, {x: 8, y: 2}, {x: 13, y: 17},
{x: 16, y: 9}, {x: -4, y: 14}, {x: 1, y: 20}, {x: 14, y: 4},
],
backgroundColor: 'rgba(54, 162, 235, 0.7)',
borderColor: 'rgb(54, 162, 235)',
borderWidth: 1,
}],
});
// Bar Chart
this.createLargeChart('bar-chart', 'bar', {
labels: ['Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange'],
datasets: [{
label: '# of Votes',
data: [12, 19, 3, 5, 2, 3],
backgroundColor: [
'rgba(255, 99, 132, 0.6)',
'rgba(54, 162, 235, 0.6)',
'rgba(255, 205, 86, 0.6)',
'rgba(75, 192, 192, 0.6)',
'rgba(153, 102, 255, 0.6)',
'rgba(255, 159, 64, 0.6)',
],
borderColor: [
'rgba(255, 99, 132, 1)',
'rgba(54, 162, 235, 1)',
'rgba(255, 205, 86, 1)',
'rgba(75, 192, 192, 1)',
'rgba(153, 102, 255, 1)',
'rgba(255, 159, 64, 1)',
],
borderWidth: 1,
}],
});
// Doughnut Chart
this.createLargeChart('doughnut-chart', 'doughnut', {
labels: ['Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange'],
datasets: [{
label: 'My First Dataset',
data: [300, 50, 100, 75, 120, 60],
backgroundColor: [
'rgba(255, 99, 132, 0.8)',
'rgba(54, 162, 235, 0.8)',
'rgba(255, 205, 86, 0.8)',
'rgba(75, 192, 192, 0.8)',
'rgba(153, 102, 255, 0.8)',
'rgba(255, 159, 64, 0.8)',
],
borderColor: [
'rgba(255, 99, 132, 1)',
'rgba(54, 162, 235, 1)',
'rgba(255, 205, 86, 1)',
'rgba(75, 192, 192, 1)',
'rgba(153, 102, 255, 1)',
'rgba(255, 159, 64, 1)',
],
borderWidth: 2,
hoverOffset: 10,
}],
});
// Polar Area Chart
this.createLargeChart('polar-chart', 'polarArea', {
labels: ['Red', 'Green', 'Yellow', 'Grey', 'Blue'],
datasets: [{
label: 'My First Dataset',
data: [11, 16, 7, 3, 14],
backgroundColor: [
'rgba(255, 99, 132, 0.7)',
'rgba(75, 192, 192, 0.7)',
'rgba(255, 205, 86, 0.7)',
'rgba(201, 203, 207, 0.7)',
'rgba(54, 162, 235, 0.7)',
],
borderColor: [
'rgb(255, 99, 132)',
'rgb(75, 192, 192)',
'rgb(255, 205, 86)',
'rgb(201, 203, 207)',
'rgb(54, 162, 235)',
],
borderWidth: 2,
}],
});
// Radar Chart
this.createLargeChart('radar-chart', 'radar', {
labels: ['Speed', 'Reliability', 'Comfort', 'Safety', 'Efficiency', 'Innovation'],
datasets: [{
label: 'Product A',
data: [65, 59, 90, 81, 56, 55],
fill: true,
backgroundColor: 'rgba(54, 162, 235, 0.2)',
borderColor: 'rgb(54, 162, 235)',
borderWidth: 2,
pointBackgroundColor: 'rgb(54, 162, 235)',
pointBorderColor: '#fff',
pointHoverBackgroundColor: '#fff',
pointHoverBorderColor: 'rgb(54, 162, 235)',
}, {
label: 'Product B',
data: [28, 48, 40, 95, 86, 27],
fill: true,
backgroundColor: 'rgba(255, 99, 132, 0.2)',
borderColor: 'rgb(255, 99, 132)',
borderWidth: 2,
pointBackgroundColor: 'rgb(255, 99, 132)',
pointBorderColor: '#fff',
pointHoverBackgroundColor: '#fff',
pointHoverBorderColor: 'rgb(255, 99, 132)',
}],
});
// Mixed Chart (Bar + Line)
this.createLargeChart('mixed-chart', 'bar', {
labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'],
datasets: [{
type: 'bar',
label: 'Sales',
data: [12, 19, 3, 5, 2, 3],
backgroundColor: 'rgba(54, 162, 235, 0.7)',
borderColor: 'rgb(54, 162, 235)',
borderWidth: 1,
}, {
type: 'line',
label: 'Revenue',
data: [18, 25, 8, 15, 12, 18],
fill: false,
borderColor: 'rgb(255, 99, 132)',
backgroundColor: 'rgba(255, 99, 132, 0.2)',
borderWidth: 3,
tension: 0.4,
pointRadius: 5,
pointHoverRadius: 7,
}],
});
// Bubble Chart
this.createLargeChart('bubble-chart', 'bubble', {
datasets: [{
label: 'First Dataset',
data: [
{x: 20, y: 30, r: 15},
{x: 40, y: 10, r: 10},
{x: 30, y: 40, r: 20},
{x: 50, y: 35, r: 12},
{x: 10, y: 50, r: 8},
{x: 60, y: 20, r: 18},
{x: 25, y: 25, r: 14},
],
backgroundColor: 'rgba(54, 162, 235, 0.6)',
borderColor: 'rgb(54, 162, 235)',
borderWidth: 2,
}, {
label: 'Second Dataset',
data: [
{x: 15, y: 45, r: 12},
{x: 35, y: 15, r: 16},
{x: 45, y: 25, r: 9},
{x: 55, y: 45, r: 14},
{x: 25, y: 35, r: 11},
],
backgroundColor: 'rgba(255, 99, 132, 0.6)',
borderColor: 'rgb(255, 99, 132)',
borderWidth: 2,
}],
});
}
/**
* Create large chart for charts page
*/
createLargeChart(id, type, data) {
const canvas = document.getElementById(id);
if (!canvas) return;
const ctx = canvas.getContext('2d');
// Define chart-specific options
const chartOptions = this.getChartOptions(type);
const chart = new Chart(ctx, {
type,
data,
options: chartOptions,
});
this.charts.set(id, chart);
}
/**
* Get chart-specific options based on chart type
*/
getChartOptions(type) {
const baseOptions = {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: true,
position: 'top',
labels: {
padding: 20,
font: {
size: 12,
weight: '600',
},
},
},
tooltip: {
enabled: true,
cornerRadius: 8,
displayColors: true,
},
},
};
// Chart type specific configurations
switch (type) {
case 'doughnut':
case 'pie':
return {
...baseOptions,
plugins: {
...baseOptions.plugins,
legend: {
...baseOptions.plugins.legend,
position: 'right',
},
},
interaction: {
intersect: false,
},
};
case 'polarArea':
return {
...baseOptions,
scales: {
r: {
pointLabels: {
display: true,
centerPointLabels: true,
font: {
size: 10,
},
},
grid: {
},
},
},
};
case 'radar':
return {
...baseOptions,
scales: {
r: {
angleLines: {
display: true,
},
grid: {
},
pointLabels: {
font: {
size: 11,
},
},
ticks: {
display: true,
font: {
size: 10,
},
},
},
},
};
case 'bubble':
return {
...baseOptions,
scales: {
x: {
type: 'linear',
position: 'bottom',
grid: {
borderDash: [5, 5],
},
ticks: {
font: {
size: 11,
},
},
},
y: {
beginAtZero: true,
grid: {
borderDash: [5, 5],
},
ticks: {
font: {
size: 11,
},
},
},
},
plugins: {
...baseOptions.plugins,
tooltip: {
...baseOptions.plugins.tooltip,
callbacks: {
label(context) {
return `${context.dataset.label}: (${context.parsed.x}, ${context.parsed.y}), Size: ${context.parsed._custom}`;
},
},
},
},
};
case 'scatter':
return {
...baseOptions,
scales: {
x: {
type: 'linear',
position: 'bottom',
grid: {
borderDash: [5, 5],
},
ticks: {
font: {
size: 11,
},
},
},
y: {
grid: {
borderDash: [5, 5],
},
ticks: {
font: {
size: 11,
},
},
},
},
};
default:
// For line, bar, area, mixed charts
return {
...baseOptions,
scales: {
x: {
grid: {
borderDash: [5, 5],
},
ticks: {
font: {
size: 11,
},
},
},
y: {
beginAtZero: true,
grid: {
borderDash: [5, 5],
},
ticks: {
font: {
size: 11,
},
},
},
},
};
}
}
/**
* Create tristate chart (for .sparktri elements)
*/
createTristateChart(element, data, id) {
let canvas = element.querySelector('canvas');
if (!canvas) {
canvas = document.createElement('canvas');
canvas.width = 100;
canvas.height = 20;
canvas.style.width = '100px';
canvas.style.height = '20px';
element.appendChild(canvas);
}
const ctx = canvas.getContext('2d');
const chart = new Chart(ctx, {
type: 'bar',
data: {
labels: data.map((_, i) => i),
datasets: [{
data: data.map(val => Math.abs(val)),
backgroundColor: data.map(val => {
if (val > 0) return COLORS['light-blue-500'];
if (val < 0) return '#f90';
return '#000';
}),
borderColor: data.map(val => {
if (val > 0) return COLORS['light-blue-500'];
if (val < 0) return '#f90';
return '#000';
}),
borderWidth: 1,
barPercentage: 0.8,
}],
},
options: {
responsive: false,
maintainAspectRatio: false,
scales: {
x: { display: false },
y: { display: false },
},
plugins: {
legend: { display: false },
tooltip: {
enabled: true,
callbacks: {
label: (context) => `${context.parsed.y}°Celsius`,
},
},
},
},
});
this.charts.set(id, chart);
}
/**
* Create discrete chart (for .sparkdisc elements)
*/
createDiscreteChart(element, data, id) {
let canvas = element.querySelector('canvas');
if (!canvas) {
canvas = document.createElement('canvas');
canvas.width = 100;
canvas.height = 20;
canvas.style.width = '100px';
canvas.style.height = '20px';
element.appendChild(canvas);
}
const ctx = canvas.getContext('2d');
const chart = new Chart(ctx, {
type: 'scatter',
data: {
datasets: [{
data: data.map((val, index) => ({x: index, y: val})),
backgroundColor: '#9f0',
borderColor: '#9f0',
pointRadius: 2,
showLine: false,
}],
},
options: {
responsive: false,
maintainAspectRatio: false,
scales: {
x: { display: false },
y: { display: false },
},
plugins: {
legend: { display: false },
tooltip: {
enabled: true,
callbacks: {
label: (context) => `${context.parsed.y}°Celsius`,
},
},
},
},
});
this.charts.set(id, chart);
}
/**
* Create bullet chart (for .sparkbull elements)
*/
createBulletChart(element, data, id) {
let canvas = element.querySelector('canvas');
if (!canvas) {
canvas = document.createElement('canvas');
canvas.width = 100;
canvas.height = 20;
canvas.style.width = '100px';
canvas.style.height = '20px';
element.appendChild(canvas);
}
const ctx = canvas.getContext('2d');
// Simplified bullet chart as horizontal bar
const chart = new Chart(ctx, {
type: 'bar',
data: {
labels: [''],
datasets: [{
data: [Math.max(...data)],
backgroundColor: COLORS['amber-500'],
borderColor: COLORS['amber-500'],
borderWidth: 1,
barPercentage: 0.6,
}],
},
options: {
responsive: false,
maintainAspectRatio: false,
indexAxis: 'y',
scales: {
x: { display: false },
y: { display: false },
},
plugins: {
legend: { display: false },
tooltip: {
enabled: true,
callbacks: {
label: (context) => `${context.parsed.x}°Celsius`,
},
},
},
},
});
this.charts.set(id, chart);
}
/**
* Create box chart (for .sparkbox elements)
*/
createBoxChart(element, data, id) {
let canvas = element.querySelector('canvas');
if (!canvas) {
canvas = document.createElement('canvas');
canvas.width = 100;
canvas.height = 20;
canvas.style.width = '100px';
canvas.style.height = '20px';
element.appendChild(canvas);
}
const ctx = canvas.getContext('2d');
// Box plot simplified as bar chart showing quartiles
const sortedData = [...data].sort((a, b) => a - b);
const q1 = sortedData[Math.floor(sortedData.length * 0.25)];
const median = sortedData[Math.floor(sortedData.length * 0.5)];
const q3 = sortedData[Math.floor(sortedData.length * 0.75)];
const chart = new Chart(ctx, {
type: 'bar',
data: {
labels: ['Q1', 'Med', 'Q3'],
datasets: [{
data: [q1, median, q3],
backgroundColor: '#9f0',
borderColor: '#9f0',
borderWidth: 1,
barPercentage: 0.8,
}],
},
options: {
responsive: false,
maintainAspectRatio: false,
scales: {
x: { display: false },
y: { display: false },
},
plugins: {
legend: { display: false },
tooltip: {
enabled: true,
callbacks: {
label: (context) => `${context.parsed.y}°Celsius`,
},
},
},
},
});
this.charts.set(id, chart);
}
/**
* Create Easy Pie Charts (replaces jQuery Easy Pie Chart)
*/
createEasyPieCharts() {
const easyPieElements = document.querySelectorAll('.easy-pie-chart');
easyPieElements.forEach((element, index) => {
const size = parseInt(element.dataset.size) || 80;
const percent = parseInt(element.dataset.percent) || 0;
const barColor = element.dataset.barColor || '#f44336';
// Create canvas for the pie chart
let canvas = element.querySelector('canvas');
if (!canvas) {
canvas = document.createElement('canvas');
canvas.width = size;
canvas.height = size;
canvas.style.width = `${size}px`;
canvas.style.height = `${size}px`;
element.appendChild(canvas);
}
// Create percentage display
const percentDisplay = element.querySelector('span');
if (percentDisplay) {
percentDisplay.textContent = `${percent}%`;
percentDisplay.style.position = 'absolute';
percentDisplay.style.top = '50%';
percentDisplay.style.left = '50%';
percentDisplay.style.transform = 'translate(-50%, -50%)';
percentDisplay.style.fontSize = '14px';
percentDisplay.style.fontWeight = 'bold';
}
// Set element position to relative for absolute positioning of text
element.style.position = 'relative';
element.style.display = 'inline-block';
const ctx = canvas.getContext('2d');
const chart = new Chart(ctx, {
type: 'doughnut',
data: {
datasets: [{
data: [percent, 100 - percent],
backgroundColor: [barColor, '#f0f0f0'],
borderWidth: 0,
cutout: '70%',
}],
},
options: {
responsive: false,
maintainAspectRatio: false,
plugins: {
legend: { display: false },
tooltip: { enabled: false },
},
},
});
this.charts.set(`easy-pie-${index}`, chart);
});
}
/**
* Destroy all charts
*/
destroy() {
this.charts.forEach(chart => {
chart.destroy();
});
this.charts.clear();
if (this.debounceTimer) {
clearTimeout(this.debounceTimer);
}
}
}
export default ChartComponent;
================================================
FILE: src/assets/scripts/components/Sidebar.js
================================================
/**
* Modern Sidebar Component
* Replaces jQuery-based sidebar functionality with vanilla JavaScript
*/
class Sidebar {
constructor() {
this.sidebar = document.querySelector('.sidebar');
this.sidebarMenu = document.querySelector('.sidebar .sidebar-menu');
this.sidebarToggleLinks = document.querySelectorAll('.sidebar-toggle a');
this.sidebarToggleById = document.querySelector('#sidebar-toggle');
this.app = document.querySelector('.app');
this.init();
}
init() {
if (!this.sidebar || !this.sidebarMenu) {
return;
}
this.setupMenuToggle();
this.setupSidebarToggle();
this.setActiveLink();
}
/**
* Setup dropdown menu functionality
*/
setupMenuToggle() {
const menuLinks = this.sidebarMenu.querySelectorAll('li a');
menuLinks.forEach(link => {
link.addEventListener('click', (e) => {
const listItem = link.parentElement;
const dropdownMenu = listItem.querySelector('.dropdown-menu');
// If this is a regular navigation link (not dropdown), allow normal navigation
if (!dropdownMenu) {
// Don't prevent default for regular navigation links
return;
}
// Only prevent default for dropdown toggles
e.preventDefault();
if (listItem.classList.contains('open')) {
this.closeDropdown(listItem, dropdownMenu);
} else {
this.closeAllDropdowns();
this.openDropdown(listItem, dropdownMenu);
}
});
});
}
/**
* Open dropdown with smooth animation
*/
openDropdown(listItem, dropdownMenu) {
listItem.classList.add('open');
dropdownMenu.style.display = 'block';
dropdownMenu.style.height = '0px';
dropdownMenu.style.overflow = 'hidden';
// Get the natural height
const height = dropdownMenu.scrollHeight;
// Animate to full height
dropdownMenu.animate([
{ height: '0px' },
{ height: `${height}px` },
], {
duration: 200,
easing: 'ease-out',
}).onfinish = () => {
dropdownMenu.style.height = 'auto';
dropdownMenu.style.overflow = 'visible';
};
}
/**
* Close dropdown with smooth animation
*/
closeDropdown(listItem, dropdownMenu) {
const height = dropdownMenu.scrollHeight;
dropdownMenu.style.height = `${height}px`;
dropdownMenu.style.overflow = 'hidden';
dropdownMenu.animate([
{ height: `${height}px` },
{ height: '0px' },
], {
duration: 200,
easing: 'ease-in',
}).onfinish = () => {
listItem.classList.remove('open');
dropdownMenu.style.display = 'none';
dropdownMenu.style.height = '';
dropdownMenu.style.overflow = '';
};
}
/**
* Close all open dropdowns
*/
closeAllDropdowns() {
const openItems = this.sidebarMenu.querySelectorAll('li.open');
openItems.forEach(item => {
const dropdownMenu = item.querySelector('.dropdown-menu');
if (dropdownMenu) {
this.closeDropdown(item, dropdownMenu);
}
// Also remove the has-active-child class
item.classList.remove('has-active-child');
});
}
/**
* Setup sidebar toggle functionality
*/
setupSidebarToggle() {
// Handle mobile sidebar toggle links (inside .sidebar-toggle divs)
this.sidebarToggleLinks.forEach(link => {
if (link && this.app) {
link.addEventListener('click', (e) => {
e.preventDefault();
this.toggleSidebar();
});
}
});
// Handle the main topbar sidebar toggle
if (this.sidebarToggleById && this.app) {
this.sidebarToggleById.addEventListener('click', (e) => {
e.preventDefault();
this.toggleSidebar();
});
}
}
/**
* Toggle sidebar and handle resize events properly
*/
toggleSidebar() {
this.app.classList.toggle('is-collapsed');
// Only trigger resize for masonry, but avoid chart redraw issues
setTimeout(() => {
// Dispatch a custom event instead of generic resize to avoid chart issues
window.dispatchEvent(new CustomEvent('sidebar:toggle', {
detail: { collapsed: this.app.classList.contains('is-collapsed') },
}));
// Still trigger resize for masonry but with a specific check
if (window.EVENT) {
window.dispatchEvent(window.EVENT);
}
}, 300);
}
/**
* Set active link based on current URL
*/
setActiveLink() {
// Remove active class from all nav items (including dropdown items)
const allNavItems = this.sidebar.querySelectorAll('.nav-item');
allNavItems.forEach(item => {
item.classList.remove('actived');
});
// Close all dropdowns first
this.closeAllDropdowns();
// Get current page filename
const currentPath = window.location.pathname;
const currentPage = currentPath.split('/').pop() || 'index.html';
// Find and activate the correct nav item
const allLinks = this.sidebar.querySelectorAll('a[href]');
allLinks.forEach(link => {
const href = link.getAttribute('href');
if (!href || href === 'javascript:void(0);' || href === 'javascript:void(0)') return;
// Extract filename from href
const linkPage = href.split('/').pop();
if (linkPage === currentPage) {
const navItem = link.closest('.nav-item');
if (navItem) {
navItem.classList.add('actived');
// If this is inside a dropdown, handle parent dropdown specially
const parentDropdown = navItem.closest('.dropdown-menu');
if (parentDropdown) {
const parentDropdownItem = parentDropdown.closest('.nav-item.dropdown');
if (parentDropdownItem) {
// Open the parent dropdown
parentDropdownItem.classList.add('open');
parentDropdown.style.display = 'block';
// Add special styling to indicate parent has active child
parentDropdownItem.classList.add('has-active-child');
}
}
}
}
});
}
/**
* Public method to refresh active links (useful for SPA navigation)
*/
refreshActiveLink() {
this.setActiveLink();
}
/**
* Public method to toggle sidebar programmatically
*/
toggle() {
if (this.app) {
this.app.classList.toggle('is-collapsed');
}
}
/**
* Public method to check if sidebar is collapsed
*/
isCollapsed() {
return this.app ? this.app.classList.contains('is-collapsed') : false;
}
}
export default Sidebar;
================================================
FILE: src/assets/scripts/constants/colors.js
================================================
const COLORS = {
'white' : '#ffffff',
'red-50' : '#ffebee',
'red-100' : '#ffcdd2',
'red-200' : '#ef9a9a',
'red-300' : '#e57373',
'red-400' : '#ef5350',
'red-500' : '#f44336',
'red-600' : '#e53935',
'red-700' : '#d32f2f',
'red-800' : '#c62828',
'red-900' : '#b71c1c',
'red-a100' : '#ff8a80',
'red-a200' : '#ff5252',
'red-a400' : '#ff1744',
'red-a700' : '#d50000',
'pink-50' : '#fce4ec',
'pink-100' : '#f8bbd0',
'pink-200' : '#f48fb1',
'pink-300' : '#f06292',
'pink-400' : '#ec407a',
'pink-500' : '#e91e63',
'pink-600' : '#d81b60',
'pink-700' : '#c2185b',
'pink-800' : '#ad1457',
'pink-900' : '#880e4f',
'pink-a100' : '#ff80ab',
'pink-a200' : '#ff4081',
'pink-a400' : '#f50057',
'pink-a700' : '#c51162',
'purple-50' : '#f3e5f5',
'purple-100' : '#e1bee7',
'purple-200' : '#ce93d8',
'purple-300' : '#ba68c8',
'purple-400' : '#ab47bc',
'purple-500' : '#9c27b0',
'purple-600' : '#8e24aa',
'purple-700' : '#7b1fa2',
'purple-800' : '#6a1b9a',
'purple-900' : '#4a148c',
'purple-a100' : '#ea80fc',
'purple-a200' : '#e040fb',
'purple-a400' : '#d500f9',
'purple-a700' : '#aa00ff',
'deep-purple-50' : '#ede7f6',
'deep-purple-100' : '#d1c4e9',
'deep-purple-200' : '#b39ddb',
'deep-purple-300' : '#9575cd',
'deep-purple-400' : '#7e57c2',
'deep-purple-500' : '#673ab7',
'deep-purple-600' : '#5e35b1',
'deep-purple-700' : '#512da8',
'deep-purple-800' : '#4527a0',
'deep-purple-900' : '#311b92',
'deep-purple-a100' : '#b388ff',
'deep-purple-a200' : '#7c4dff',
'deep-purple-a400' : '#651fff',
'deep-purple-a700' : '#6200ea',
'indigo-50' : '#e8eaf6',
'indigo-100' : '#c5cae9',
'indigo-200' : '#9fa8da',
'indigo-300' : '#7986cb',
'indigo-400' : '#5c6bc0',
'indigo-500' : '#3f51b5',
'indigo-600' : '#3949ab',
'indigo-700' : '#303f9f',
'indigo-800' : '#283593',
'indigo-900' : '#1a237e',
'indigo-a100' : '#8c9eff',
'indigo-a200' : '#536dfe',
'indigo-a400' : '#3d5afe',
'indigo-a700' : '#304ffe',
'blue-50' : '#e3f2fd',
'blue-100' : '#bbdefb',
'blue-200' : '#90caf9',
'blue-300' : '#64b5f6',
'blue-400' : '#42a5f5',
'blue-500' : '#2196f3',
'blue-600' : '#1e88e5',
'blue-700' : '#1976d2',
'blue-800' : '#1565c0',
'blue-900' : '#0d47a1',
'blue-a100' : '#82b1ff',
'blue-a200' : '#448aff',
'blue-a400' : '#2979ff',
'blue-a700' : '#2962ff',
'light-blue-50' : '#e1f5fe',
'light-blue-100' : '#b3e5fc',
'light-blue-200' : '#81d4fa',
'light-blue-300' : '#4fc3f7',
'light-blue-400' : '#29b6f6',
'light-blue-500' : '#03a9f4',
'light-blue-600' : '#039be5',
'light-blue-700' : '#0288d1',
'light-blue-800' : '#0277bd',
'light-blue-900' : '#01579b',
'light-blue-a100' : '#80d8ff',
'light-blue-a200' : '#40c4ff',
'light-blue-a400' : '#00b0ff',
'light-blue-a700' : '#0091ea',
'cyan-50' : '#e0f7fa',
'cyan-100' : '#b2ebf2',
'cyan-200' : '#80deea',
'cyan-300' : '#4dd0e1',
'cyan-400' : '#26c6da',
'cyan-500' : '#00bcd4',
'cyan-600' : '#00acc1',
'cyan-700' : '#0097a7',
'cyan-800' : '#00838f',
'cyan-900' : '#006064',
'cyan-a100' : '#84ffff',
'cyan-a200' : '#18ffff',
'cyan-a400' : '#00e5ff',
'cyan-a700' : '#00b8d4',
'teal-50' : '#e0f2f1',
'teal-100' : '#b2dfdb',
'teal-200' : '#80cbc4',
'teal-300' : '#4db6ac',
'teal-400' : '#26a69a',
'teal-500' : '#009688',
'teal-600' : '#00897b',
'teal-700' : '#00796b',
'teal-800' : '#00695c',
'teal-900' : '#004d40',
'teal-a100' : '#a7ffeb',
'teal-a200' : '#64ffda',
'teal-a400' : '#1de9b6',
'teal-a700' : '#00bfa5',
'green-50' : '#e8f5e9',
'green-100' : '#c8e6c9',
'green-200' : '#a5d6a7',
'green-300' : '#81c784',
'green-400' : '#66bb6a',
'green-500' : '#4caf50',
'green-600' : '#43a047',
'green-700' : '#388e3c',
'green-800' : '#2e7d32',
'green-900' : '#1b5e20',
'green-a100' : '#b9f6ca',
'green-a200' : '#69f0ae',
'green-a400' : '#00e676',
'green-a700' : '#00c853',
'light-green-50' : '#f1f8e9',
'light-green-100' : '#dcedc8',
'light-green-200' : '#c5e1a5',
'light-green-300' : '#aed581',
'light-green-400' : '#9ccc65',
'light-green-500' : '#8bc34a',
'light-green-600' : '#7cb342',
'light-green-700' : '#689f38',
'light-green-800' : '#558b2f',
'light-green-900' : '#33691e',
'light-green-a100' : '#ccff90',
'light-green-a200' : '#b2ff59',
'light-green-a400' : '#76ff03',
'light-green-a700' : '#64dd17',
'lime-50' : '#f9fbe7',
'lime-100' : '#f0f4c3',
'lime-200' : '#e6ee9c',
'lime-300' : '#dce775',
'lime-400' : '#d4e157',
'lime-500' : '#cddc39',
'lime-600' : '#c0ca33',
'lime-700' : '#afb42b',
'lime-800' : '#9e9d24',
'lime-900' : '#827717',
'lime-a100' : '#f4ff81',
'lime-a200' : '#eeff41',
'lime-a400' : '#c6ff00',
'lime-a700' : '#aeea00',
'yellow-50' : '#fffde7',
'yellow-100' : '#fff9c4',
'yellow-200' : '#fff59d',
'yellow-300' : '#fff176',
'yellow-400' : '#ffee58',
'yellow-500' : '#ffeb3b',
'yellow-600' : '#fdd835',
'yellow-700' : '#fbc02d',
'yellow-800' : '#f9a825',
'yellow-900' : '#f57f17',
'yellow-a100' : '#ffff8d',
'yellow-a200' : '#ffff00',
'yellow-a400' : '#ffea00',
'yellow-a700' : '#ffd600',
'amber-50' : '#fff8e1',
'amber-100' : '#ffecb3',
'amber-200' : '#ffe082',
'amber-300' : '#ffd54f',
'amber-400' : '#ffca28',
'amber-500' : '#ffc107',
'amber-600' : '#ffb300',
'amber-700' : '#ffa000',
'amber-800' : '#ff8f00',
'amber-900' : '#ff6f00',
'amber-a100' : '#ffe57f',
'amber-a200' : '#ffd740',
'amber-a400' : '#ffc400',
'amber-a700' : '#ffab00',
'orange-50' : '#fff3e0',
'orange-100' : '#ffe0b2',
'orange-200' : '#ffcc80',
'orange-300' : '#ffb74d',
'orange-400' : '#ffa726',
'orange-500' : '#ff9800',
'orange-600' : '#fb8c00',
'orange-700' : '#f57c00',
'orange-800' : '#ef6c00',
'orange-900' : '#e65100',
'orange-a100' : '#ffd180',
'orange-a200' : '#ffab40',
'orange-a400' : '#ff9100',
'orange-a700' : '#ff6d00',
'deep-orange-50' : '#fbe9e7',
'deep-orange-100' : '#ffccbc',
'deep-orange-200' : '#ffab91',
'deep-orange-300' : '#ff8a65',
'deep-orange-400' : '#ff7043',
'deep-orange-500' : '#ff5722',
'deep-orange-600' : '#f4511e',
'deep-orange-700' : '#e64a19',
'deep-orange-800' : '#d84315',
'deep-orange-900' : '#bf360c',
'deep-orange-a100' : '#ff9e80',
'deep-orange-a200' : '#ff6e40',
'deep-orange-a400' : '#ff3d00',
'deep-orange-a700' : '#dd2c00',
'brown-50' : '#efebe9',
'brown-100' : '#d7ccc8',
'brown-200' : '#bcaaa4',
'brown-300' : '#a1887f',
'brown-400' : '#8d6e63',
'brown-500' : '#795548',
'brown-600' : '#6d4c41',
'brown-700' : '#5d4037',
'brown-800' : '#4e342e',
'brown-900' : '#3e2723',
'grey-50' : '#fafafa',
'grey-100' : '#f5f5f5',
'grey-200' : '#eeeeee',
'grey-300' : '#e0e0e0',
'grey-400' : '#bdbdbd',
'grey-500' : '#9e9e9e',
'grey-600' : '#757575',
'grey-700' : '#616161',
'grey-800' : '#424242',
'grey-900' : '#212121',
'blue-grey-50' : '#eceff1',
'blue-grey-100' : '#cfd8dc',
'blue-grey-200' : '#b0bec5',
'blue-grey-300' : '#90a4ae',
'blue-grey-400' : '#78909c',
'blue-grey-500' : '#607d8b',
'blue-grey-600' : '#546e7a',
'blue-grey-700' : '#455a64',
'blue-grey-800' : '#37474f',
'blue-grey-900' : '#263238',
};
const GREYS = {
'grey-100' : '#f9fafb',
'grey-200' : '#f2f3f5',
'grey-300' : '#e6eaf0',
'grey-400' : '#d3d9e3',
'grey-500' : '#b9c2d0',
'grey-600' : '#7c8695',
'grey-700' : '#72777a',
'grey-800' : '#565a5c',
'grey-900' : '#313435',
};
export {
COLORS,
GREYS,
};
================================================
FILE: src/assets/scripts/datatable/index.js
================================================
// DataTable implementation
export default (function () {
// Vanilla JS DataTable implementation
class VanillaDataTable {
constructor(element, options = {}) {
this.element = element;
this.options = {
sortable: true,
searchable: true,
pagination: true,
pageSize: 10,
...options,
};
this.originalData = [];
this.filteredData = [];
this.currentPage = 1;
this.sortColumn = null;
this.sortDirection = 'asc';
this.init();
}
init() {
this.extractData();
this.createControls();
this.applyStyles();
this.bindEvents();
this.render();
}
extractData() {
const rows = this.element.querySelectorAll('tbody tr');
this.originalData = Array.from(rows).map(row => {
const cells = row.querySelectorAll('td');
return Array.from(cells).map(cell => cell.textContent.trim());
});
this.filteredData = [...this.originalData];
}
createControls() {
const wrapper = document.createElement('div');
wrapper.className = 'datatable-wrapper';
// Create search input
if (this.options.searchable) {
const searchWrapper = document.createElement('div');
searchWrapper.className = 'datatable-search';
searchWrapper.innerHTML = `
Search:
`;
wrapper.appendChild(searchWrapper);
}
// Create pagination info
if (this.options.pagination) {
const infoWrapper = document.createElement('div');
infoWrapper.className = 'datatable-info';
wrapper.appendChild(infoWrapper);
}
// Wrap the table
this.element.parentNode.insertBefore(wrapper, this.element);
wrapper.appendChild(this.element);
// Create pagination controls
if (this.options.pagination) {
const paginationWrapper = document.createElement('div');
paginationWrapper.className = 'datatable-pagination';
wrapper.appendChild(paginationWrapper);
}
this.wrapper = wrapper;
}
applyStyles() {
// Apply Bootstrap-like styles
this.element.classList.add('table', 'table-striped', 'table-bordered');
// Add custom styles
const style = document.createElement('style');
style.textContent = `
.datatable-wrapper {
margin: 20px 0;
}
.datatable-search {
margin-bottom: 15px;
}
.datatable-search input {
width: 250px;
display: inline-block;
margin-left: 5px;
}
.datatable-info {
margin-top: 15px;
color: var(--c-text-muted, #6c757d);
font-size: 14px;
}
.datatable-pagination {
margin-top: 15px;
display: flex;
justify-content: center;
}
.datatable-pagination button {
background: var(--c-bkg-card, #fff);
border: 1px solid var(--c-border, #dee2e6);
color: var(--c-text-base, #333);
padding: 6px 12px;
margin: 0 2px;
cursor: pointer;
border-radius: 4px;
}
.datatable-pagination button:hover {
background: var(--c-primary, #007bff);
color: white;
}
.datatable-pagination button.active {
background: var(--c-primary, #007bff);
color: white;
}
.datatable-pagination button:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.datatable-sort {
cursor: pointer;
user-select: none;
position: relative;
}
.datatable-sort:hover {
background: var(--c-bkg-card, #f8f9fa);
}
.datatable-sort::after {
content: '↕';
position: absolute;
right: 8px;
opacity: 0.5;
}
.datatable-sort.asc::after {
content: '↑';
opacity: 1;
}
.datatable-sort.desc::after {
content: '↓';
opacity: 1;
}
`;
document.head.appendChild(style);
}
bindEvents() {
// Search functionality
if (this.options.searchable) {
const searchInput = this.wrapper.querySelector('.datatable-search input');
searchInput.addEventListener('input', (e) => {
this.search(e.target.value);
});
}
// Sorting functionality
if (this.options.sortable) {
const headers = this.element.querySelectorAll('thead th');
headers.forEach((header, index) => {
header.classList.add('datatable-sort');
header.addEventListener('click', () => {
this.sort(index);
});
});
}
}
search(query) {
if (!query) {
this.filteredData = [...this.originalData];
} else {
this.filteredData = this.originalData.filter(row =>
row.some(cell =>
cell.toLowerCase().includes(query.toLowerCase())
)
);
}
this.currentPage = 1;
this.render();
}
sort(columnIndex) {
if (this.sortColumn === columnIndex) {
this.sortDirection = this.sortDirection === 'asc' ? 'desc' : 'asc';
} else {
this.sortColumn = columnIndex;
this.sortDirection = 'asc';
}
this.filteredData.sort((a, b) => {
const aVal = a[columnIndex];
const bVal = b[columnIndex];
// Try to parse as numbers
const aNum = parseFloat(aVal);
const bNum = parseFloat(bVal);
let comparison = 0;
if (!isNaN(aNum) && !isNaN(bNum)) {
comparison = aNum - bNum;
} else {
comparison = aVal.localeCompare(bVal);
}
return this.sortDirection === 'asc' ? comparison : -comparison;
});
this.updateSortHeaders();
this.render();
}
updateSortHeaders() {
const headers = this.element.querySelectorAll('thead th');
headers.forEach((header, index) => {
header.classList.remove('asc', 'desc');
if (index === this.sortColumn) {
header.classList.add(this.sortDirection);
}
});
}
render() {
const tbody = this.element.querySelector('tbody');
const startIndex = (this.currentPage - 1) * this.options.pageSize;
const endIndex = startIndex + this.options.pageSize;
const pageData = this.filteredData.slice(startIndex, endIndex);
// Clear tbody
tbody.innerHTML = '';
// Add rows
pageData.forEach(rowData => {
const row = document.createElement('tr');
rowData.forEach(cellData => {
const cell = document.createElement('td');
cell.textContent = cellData;
row.appendChild(cell);
});
tbody.appendChild(row);
});
// Update pagination
if (this.options.pagination) {
this.updatePagination();
}
// Update info
this.updateInfo();
}
updatePagination() {
const totalPages = Math.ceil(this.filteredData.length / this.options.pageSize);
const paginationWrapper = this.wrapper.querySelector('.datatable-pagination');
paginationWrapper.innerHTML = '';
if (totalPages <= 1) return;
// Previous button
const prevBtn = document.createElement('button');
prevBtn.textContent = 'Previous';
prevBtn.disabled = this.currentPage === 1;
prevBtn.addEventListener('click', () => {
if (this.currentPage > 1) {
this.currentPage--;
this.render();
}
});
paginationWrapper.appendChild(prevBtn);
// Page numbers
for (let i = 1; i <= totalPages; i++) {
const pageBtn = document.createElement('button');
pageBtn.textContent = i;
pageBtn.classList.toggle('active', i === this.currentPage);
pageBtn.addEventListener('click', () => {
this.currentPage = i;
this.render();
});
paginationWrapper.appendChild(pageBtn);
}
// Next button
const nextBtn = document.createElement('button');
nextBtn.textContent = 'Next';
nextBtn.disabled = this.currentPage === totalPages;
nextBtn.addEventListener('click', () => {
if (this.currentPage < totalPages) {
this.currentPage++;
this.render();
}
});
paginationWrapper.appendChild(nextBtn);
}
updateInfo() {
const infoWrapper = this.wrapper.querySelector('.datatable-info');
if (!infoWrapper) return;
const startIndex = (this.currentPage - 1) * this.options.pageSize + 1;
const endIndex = Math.min(startIndex + this.options.pageSize - 1, this.filteredData.length);
const total = this.filteredData.length;
infoWrapper.textContent = `Showing ${startIndex} to ${endIndex} of ${total} entries`;
}
destroy() {
if (this.wrapper && this.wrapper.parentNode) {
this.wrapper.parentNode.replaceChild(this.element, this.wrapper);
}
}
}
// Initialize DataTable
const initializeDataTable = () => {
const tableElement = document.getElementById('dataTable');
if (tableElement) {
// Clean up existing instance
if (tableElement.dataTableInstance) {
tableElement.dataTableInstance.destroy();
}
// Create new instance
const dataTable = new VanillaDataTable(tableElement, {
sortable: true,
searchable: true,
pagination: true,
pageSize: 10,
});
// Store instance for cleanup
tableElement.dataTableInstance = dataTable;
}
};
// Initialize on load
initializeDataTable();
// Reinitialize on theme change
window.addEventListener('adminator:themeChanged', () => {
setTimeout(initializeDataTable, 100);
});
// Cleanup on page unload
window.addEventListener('beforeunload', () => {
const tableElement = document.getElementById('dataTable');
if (tableElement && tableElement.dataTableInstance) {
tableElement.dataTableInstance.destroy();
}
});
// Return public API
return {
init: initializeDataTable,
VanillaDataTable,
};
}());
================================================
FILE: src/assets/scripts/datepicker/index.js
================================================
import DateUtils from '../utils/date.js';
export default (function () {
// Enhanced HTML5 date picker with vanilla JS
class VanillaDatePicker {
constructor(element, options = {}) {
this.element = element;
this.options = {
format: 'yyyy-mm-dd',
autoclose: true,
todayHighlight: true,
...options,
};
this.init();
}
init() {
this.convertToHTML5();
this.enhanceInput();
this.applyStyles();
this.bindEvents();
}
convertToHTML5() {
// Convert input to HTML5 date type
this.element.type = 'date';
this.element.classList.add('form-control', 'vanilla-datepicker');
// Remove placeholder since HTML5 date inputs don't need it
this.element.removeAttribute('placeholder');
// Set default value to today if no value is set
if (!this.element.value) {
this.element.value = DateUtils.form.toInputValue(DateUtils.now());
}
// Ensure proper styling
this.element.style.minHeight = '38px';
this.element.style.lineHeight = '1.5';
this.element.style.cursor = 'pointer';
}
enhanceInput() {
// Create wrapper for enhanced functionality
const wrapper = document.createElement('div');
wrapper.className = 'vanilla-datepicker-wrapper';
wrapper.style.position = 'relative';
// Wrap the input
this.element.parentNode.insertBefore(wrapper, this.element);
wrapper.appendChild(this.element);
// Add calendar icon if input is in an input group
const inputGroup = this.element.closest('.input-group');
if (inputGroup) {
const calendarIcon = inputGroup.querySelector('.input-group-text i.ti-calendar');
if (calendarIcon) {
calendarIcon.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
this.openPicker();
});
}
}
this.wrapper = wrapper;
}
applyStyles() {
// Add custom styles for enhanced appearance
const style = document.createElement('style');
style.textContent = `
.vanilla-datepicker-wrapper {
position: relative;
}
.vanilla-datepicker {
width: 100%;
padding: 6px 12px;
border: 1px solid var(--c-border, #ced4da);
border-radius: 4px;
background-color: var(--c-bkg-card, #fff);
color: var(--c-text-base, #495057);
font-size: 14px;
transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
}
.vanilla-datepicker:focus {
outline: none;
border-color: var(--c-primary, #007bff);
box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
}
.vanilla-datepicker::-webkit-calendar-picker-indicator {
cursor: pointer;
border-radius: 4px;
margin-right: 2px;
opacity: 0.6;
transition: opacity 0.15s ease-in-out;
}
.vanilla-datepicker::-webkit-calendar-picker-indicator:hover {
opacity: 1;
}
.vanilla-datepicker::-webkit-datetime-edit-fields-wrapper {
padding: 0;
}
.vanilla-datepicker::-webkit-datetime-edit-month-field,
.vanilla-datepicker::-webkit-datetime-edit-day-field,
.vanilla-datepicker::-webkit-datetime-edit-year-field {
color: var(--c-text-base, #495057);
}
.vanilla-datepicker::-webkit-datetime-edit-text {
color: var(--c-text-muted, #6c757d);
}
/* Dark mode support */
[data-theme="dark"] .vanilla-datepicker {
background-color: var(--c-bkg-card, #1f2937);
color: var(--c-text-base, #f9fafb);
border-color: var(--c-border, #374151);
}
[data-theme="dark"] .vanilla-datepicker::-webkit-calendar-picker-indicator {
filter: invert(1);
}
.datepicker-today-indicator {
position: absolute;
top: 2px;
right: 8px;
width: 6px;
height: 6px;
background-color: var(--c-primary, #007bff);
border-radius: 50%;
opacity: 0.8;
pointer-events: none;
}
.datepicker-animation {
animation: datepicker-highlight 0.3s ease-in-out;
}
@keyframes datepicker-highlight {
0% { transform: scale(1); }
50% { transform: scale(1.02); }
100% { transform: scale(1); }
}
`;
// Only add the style if it doesn't exist
if (!document.querySelector('style[data-vanilla-datepicker-styles]')) {
style.setAttribute('data-vanilla-datepicker-styles', 'true');
document.head.appendChild(style);
}
}
bindEvents() {
// Handle click events
this.element.addEventListener('click', () => {
this.openPicker();
});
// Handle keyboard events
this.element.addEventListener('keydown', (e) => {
if (e.key === 'Enter' || e.key === ' ') {
this.openPicker();
}
});
// Handle change events
this.element.addEventListener('change', (e) => {
this.handleDateChange(e);
});
// Handle focus events
this.element.addEventListener('focus', () => {
this.element.classList.add('datepicker-animation');
setTimeout(() => {
this.element.classList.remove('datepicker-animation');
}, 300);
});
}
openPicker() {
this.element.focus();
// Try to open the native date picker
if (this.element.showPicker && typeof this.element.showPicker === 'function') {
try {
this.element.showPicker();
} catch {
// Fallback for browsers that don't support showPicker
}
}
}
handleDateChange(e) {
const selectedDate = e.target.value;
if (selectedDate) {
// Add visual feedback
this.element.classList.add('datepicker-animation');
setTimeout(() => {
this.element.classList.remove('datepicker-animation');
}, 300);
// Trigger custom event
const changeEvent = new CustomEvent('datepicker:change', {
detail: {
date: selectedDate,
formattedDate: this.formatDate(selectedDate),
},
});
this.element.dispatchEvent(changeEvent);
}
}
formatDate(dateString) {
const date = new Date(dateString);
return DateUtils.format(date, this.options.format);
}
setDate(dateString) {
this.element.value = dateString;
this.handleDateChange({ target: this.element });
}
getDate() {
return this.element.value;
}
destroy() {
if (this.wrapper && this.wrapper.parentNode) {
this.wrapper.parentNode.replaceChild(this.element, this.wrapper);
}
}
}
// Initialize date pickers
const initializeDatePickers = () => {
// Start date pickers
const startDateElements = document.querySelectorAll('.start-date');
startDateElements.forEach(element => {
if (element.vanillaDatePicker) {
element.vanillaDatePicker.destroy();
}
const datePicker = new VanillaDatePicker(element, {
format: 'yyyy-mm-dd',
autoclose: true,
todayHighlight: true,
});
element.vanillaDatePicker = datePicker;
});
// End date pickers
const endDateElements = document.querySelectorAll('.end-date');
endDateElements.forEach(element => {
if (element.vanillaDatePicker) {
element.vanillaDatePicker.destroy();
}
const datePicker = new VanillaDatePicker(element, {
format: 'yyyy-mm-dd',
autoclose: true,
todayHighlight: true,
});
element.vanillaDatePicker = datePicker;
});
};
// Initialize on load
initializeDatePickers();
// Reinitialize on theme change
window.addEventListener('adminator:themeChanged', () => {
setTimeout(initializeDatePickers, 100);
});
// Cleanup on page unload
window.addEventListener('beforeunload', () => {
document.querySelectorAll('.start-date, .end-date').forEach(element => {
if (element.vanillaDatePicker) {
element.vanillaDatePicker.destroy();
}
});
});
// Return public API
return {
init: initializeDatePickers,
VanillaDatePicker,
};
}());
================================================
FILE: src/assets/scripts/email/index.js
================================================
export default (function () {
// Email side toggle functionality
const emailSideToggle = document.querySelector('.email-side-toggle');
const emailApp = document.querySelector('.email-app');
if (emailSideToggle && emailApp) {
emailSideToggle.addEventListener('click', e => {
emailApp.classList.toggle('side-active');
e.preventDefault();
});
}
// Email list item and back to mailbox functionality
const emailListItems = document.querySelectorAll('.email-list-item, .back-to-mailbox');
const emailContent = document.querySelector('.email-content');
if (emailListItems.length > 0 && emailContent) {
emailListItems.forEach(item => {
item.addEventListener('click', e => {
emailContent.classList.toggle('open');
e.preventDefault();
});
});
}
}())
================================================
FILE: src/assets/scripts/fullcalendar/index.js
================================================
import { Calendar } from '@fullcalendar/core';
import interactionPlugin from '@fullcalendar/interaction';
import dayGridPlugin from '@fullcalendar/daygrid';
import timeGridPlugin from '@fullcalendar/timegrid';
import listPlugin from '@fullcalendar/list';
import DateUtils from '../utils/date';
document.addEventListener('DOMContentLoaded', function () {
const calendarEl = document.getElementById('calendar');
// element found in dom ?
if (calendarEl == null) {
return;
}
const calendar = new Calendar(calendarEl, {
plugins: [interactionPlugin, dayGridPlugin, timeGridPlugin, listPlugin],
headerToolbar: {
left: 'prev,next today',
center: 'title',
right: 'dayGridMonth,timeGridWeek,timeGridDay,listWeek',
},
initialDate: DateUtils.format(DateUtils.now(), 'YYYY-MM-DD'),
navLinks: true, // can click day/week names to navigate views
editable: true,
dayMaxEvents: true, // allow "more" link when too many events
events: [
{
title: 'All Day Event',
start: DateUtils.format(DateUtils.now(), 'YYYY-MM-DD'),
},
{
title: 'Long Event',
start: DateUtils.format(DateUtils.add(DateUtils.now(), 1, 'day'), 'YYYY-MM-DD'),
end: DateUtils.format(DateUtils.add(DateUtils.now(), 4, 'day'), 'YYYY-MM-DD'),
},
{
groupId: 999,
title: 'Repeating Event',
start: DateUtils.format(DateUtils.add(DateUtils.now(), 2, 'day'), 'YYYY-MM-DDTHH:mm:ss').replace(/:\d{2}$/, ':00:00').replace(/T\d{2}:\d{2}/, 'T16:00'),
},
{
groupId: 999,
title: 'Repeating Event',
start: DateUtils.format(DateUtils.add(DateUtils.now(), 9, 'day'), 'YYYY-MM-DDTHH:mm:ss').replace(/:\d{2}$/, ':00:00').replace(/T\d{2}:\d{2}/, 'T16:00'),
},
{
title: 'Conference',
start: DateUtils.format(DateUtils.add(DateUtils.now(), 5, 'day'), 'YYYY-MM-DD'),
end: DateUtils.format(DateUtils.add(DateUtils.now(), 7, 'day'), 'YYYY-MM-DD'),
},
{
title: 'Meeting',
start: DateUtils.format(DateUtils.add(DateUtils.now(), 3, 'day'), 'YYYY-MM-DDTHH:mm:ss').replace(/:\d{2}$/, ':00:00').replace(/T\d{2}:\d{2}/, 'T10:30'),
end: DateUtils.format(DateUtils.add(DateUtils.now(), 3, 'day'), 'YYYY-MM-DDTHH:mm:ss').replace(/:\d{2}$/, ':00:00').replace(/T\d{2}:\d{2}/, 'T12:30'),
},
{
title: 'Lunch',
start: DateUtils.format(DateUtils.add(DateUtils.now(), 3, 'day'), 'YYYY-MM-DDTHH:mm:ss').replace(/:\d{2}$/, ':00:00').replace(/T\d{2}:\d{2}/, 'T12:00'),
},
{
title: 'Meeting',
start: DateUtils.format(DateUtils.add(DateUtils.now(), 3, 'day'), 'YYYY-MM-DDTHH:mm:ss').replace(/:\d{2}$/, ':00:00').replace(/T\d{2}:\d{2}/, 'T14:30'),
},
{
title: 'Happy Hour',
start: DateUtils.format(DateUtils.add(DateUtils.now(), 3, 'day'), 'YYYY-MM-DDTHH:mm:ss').replace(/:\d{2}$/, ':00:00').replace(/T\d{2}:\d{2}/, 'T17:30'),
},
{
title: 'Dinner',
start: DateUtils.format(DateUtils.add(DateUtils.now(), 3, 'day'), 'YYYY-MM-DDTHH:mm:ss').replace(/:\d{2}$/, ':00:00').replace(/T\d{2}:\d{2}/, 'T20:00'),
},
{
title: 'Birthday Party',
start: DateUtils.format(DateUtils.add(DateUtils.now(), 4, 'day'), 'YYYY-MM-DDTHH:mm:ss').replace(/:\d{2}$/, ':00:00').replace(/T\d{2}:\d{2}/, 'T07:00'),
},
{
title: 'Click for Google',
url: 'http://google.com/',
start: DateUtils.format(DateUtils.add(DateUtils.now(), 14, 'day'), 'YYYY-MM-DD'),
},
],
});
calendar.render();
});
================================================
FILE: src/assets/scripts/googleMaps/index.js
================================================
import loadGoogleMapsAPI from 'load-google-maps-api';
import Theme from '../utils/theme.js';
export default (function () {
let map, marker;
const initGoogleMap = () => {
const googleMapElement = document.getElementById('google-map');
if (googleMapElement) {
loadGoogleMapsAPI({
key: 'AIzaSyDW8td30_gj6sGXjiMU0ALeMu1SDEwUnEA',
}).then(() => {
const latitude = 26.8206;
const longitude = 30.8025;
const mapZoom = 5;
const { google } = window;
const mapOptions = {
center : new google.maps.LatLng(latitude, longitude),
zoom : mapZoom,
mapTypeId : google.maps.MapTypeId.ROADMAP,
styles: [{
'featureType': 'landscape',
'stylers': [
{ 'hue' : Theme.getCSSVar('--gmap-landscape-hue') },
{ 'saturation' : 43.400000000000006 },
{ 'lightness' : 37.599999999999994 },
{ 'gamma' : 1 },
],
}, {
'featureType': 'road.highway',
'stylers': [
{ 'hue' : Theme.getCSSVar('--gmap-highway-hue') },
{ 'saturation' : -61.8 },
{ 'lightness' : 45.599999999999994 },
{ 'gamma' : 1 },
],
}, {
'featureType': 'road.arterial',
'stylers': [
{ 'hue' : Theme.getCSSVar('--gmap-road-hue') },
{ 'saturation' : -100 },
{ 'lightness' : 51.19999999999999 },
{ 'gamma' : 1 },
],
}, {
'featureType': 'road.local',
'stylers': [
{ 'hue' : Theme.getCSSVar('--gmap-road-hue') },
{ 'saturation' : -100 },
{ 'lightness' : 52 },
{ 'gamma' : 1 },
],
}, {
'featureType': 'water',
'stylers': [
{ 'hue' : Theme.getCSSVar('--gmap-water-hue') },
{ 'saturation' : -13.200000000000003 },
{ 'lightness' : 2.4000000000000057 },
{ 'gamma' : 1 },
],
}, {
'featureType': 'poi',
'stylers': [
{ 'hue' : Theme.getCSSVar('--gmap-poi-hue') },
{ 'saturation' : -1.0989010989011234 },
{ 'lightness' : 11.200000000000017 },
{ 'gamma' : 1 },
],
}],
};
map = new google.maps.Map(document.getElementById('google-map'), mapOptions);
if (marker) {
marker.setMap(null);
}
marker = new google.maps.Marker({
map,
position : new google.maps.LatLng(latitude, longitude),
visible : true,
});
});
}
};
// Initialize Google Maps
initGoogleMap();
// Listen for theme changes
window.addEventListener('adminator:themeChanged', initGoogleMap);
}())
================================================
FILE: src/assets/scripts/index.js
================================================
/**
* Adminator Admin Template
* Modern Entry Point - Phase 2 Modernization
*/
// Import the modern application
import './app.js';
// Legacy imports that haven't been modernized yet
// These will be gradually replaced in future iterations
import './datatable';
// import './datepicker'; // REMOVED: Replaced with modern day.js implementation in app.js
// Note: The following have been modernized and are now handled by app.js:
// - sidebar (now Sidebar component)
// - charts (now ChartComponent using Chart.js instead of jQuery Sparkline)
// - Basic DOM utilities (now DOM utils)
================================================
FILE: src/assets/scripts/masonry/index.js
================================================
import Masonry from 'masonry-layout';
export default (function () {
window.addEventListener('load', () => {
const masonryElement = document.querySelector('.masonry');
if (masonryElement) {
new Masonry(masonryElement, {
itemSelector: '.masonry-item',
columnWidth: '.masonry-sizer',
percentPosition: true,
});
}
});
}());
================================================
FILE: src/assets/scripts/popover/index.js
================================================
// Simple vanilla JS tooltip and popover implementation
export default (function () {
// Simple tooltip implementation
function initTooltips() {
const tooltipElements = document.querySelectorAll('[data-bs-toggle="tooltip"]');
tooltipElements.forEach(element => {
const tooltipText = element.getAttribute('data-bs-title') || element.getAttribute('title');
if (tooltipText) {
element.addEventListener('mouseenter', function() {
const tooltip = document.createElement('div');
tooltip.className = 'custom-tooltip';
tooltip.textContent = tooltipText;
tooltip.style.cssText = `
position: absolute;
background: #000;
color: #fff;
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
z-index: 1050;
pointer-events: none;
white-space: nowrap;
`;
document.body.appendChild(tooltip);
const rect = element.getBoundingClientRect();
tooltip.style.left = `${rect.left + (rect.width / 2) - (tooltip.offsetWidth / 2) }px`;
tooltip.style.top = `${rect.top - tooltip.offsetHeight - 5 }px`;
element._tooltip = tooltip;
});
element.addEventListener('mouseleave', function() {
if (element._tooltip) {
element._tooltip.remove();
element._tooltip = null;
}
});
}
});
}
// Simple popover implementation
function initPopovers() {
const popoverElements = document.querySelectorAll('[data-bs-toggle="popover"]');
popoverElements.forEach(element => {
const popoverContent = element.getAttribute('data-bs-content');
const popoverTitle = element.getAttribute('data-bs-title');
if (popoverContent) {
element.addEventListener('click', function(e) {
e.preventDefault();
// Remove existing popover
if (element._popover) {
element._popover.remove();
element._popover = null;
return;
}
const popover = document.createElement('div');
popover.className = 'custom-popover';
popover.innerHTML = `
${popoverTitle ? `${popoverTitle}
` : ''}
${popoverContent}
`;
popover.style.cssText = `
position: absolute;
background: #fff;
border: 1px solid #ccc;
border-radius: 6px;
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
z-index: 1050;
min-width: 200px;
max-width: 300px;
`;
document.body.appendChild(popover);
const rect = element.getBoundingClientRect();
popover.style.left = `${rect.left }px`;
popover.style.top = `${rect.bottom + 5 }px`;
element._popover = popover;
});
}
});
}
// Initialize both
initTooltips();
initPopovers();
// Close popovers when clicking outside
document.addEventListener('click', function(e) {
const popovers = document.querySelectorAll('.custom-popover');
popovers.forEach(popover => {
if (!popover.contains(e.target)) {
popover.remove();
}
});
});
}());
================================================
FILE: src/assets/scripts/scrollbar/index.js
================================================
import PerfectScrollbar from 'perfect-scrollbar';
export default (function () {
const scrollables = document.querySelectorAll('.scrollable');
if (scrollables.length > 0) {
scrollables.forEach(el => {
new PerfectScrollbar(el);
});
}
}());
================================================
FILE: src/assets/scripts/search/index.js
================================================
export default (function () {
const searchToggle = document.querySelector('.search-toggle');
const searchBox = document.querySelector('.search-box');
const searchInput = document.querySelector('.search-input');
const searchInputField = document.querySelector('.search-input input');
if (searchToggle && searchBox && searchInput && searchInputField) {
searchToggle.addEventListener('click', e => {
searchBox.classList.toggle('active');
searchInput.classList.toggle('active');
searchInputField.focus();
e.preventDefault();
});
}
}());
================================================
FILE: src/assets/scripts/skycons/index.js
================================================
import SkyconsInit from 'skycons';
import Theme from '../utils/theme.js';
const Skycons = SkyconsInit(window);
export default (function () {
let icons;
const initSkycons = () => {
const skyconsColor = Theme.getCSSVar('--skycons-color');
if (icons) {
icons.pause();
icons.remove('all');
}
icons = new Skycons({ 'color': skyconsColor });
const list = [
'clear-day',
'clear-night',
'partly-cloudy-day',
'partly-cloudy-night',
'cloudy',
'rain',
'sleet',
'snow',
'wind',
'fog',
];
let i = list.length;
while (i--) {
const
weatherType = list[i],
elements = document.getElementsByClassName(weatherType);
let j = elements.length;
while (j--) {
icons.set(elements[j], weatherType);
}
}
icons.play();
};
// Initialize skycons
initSkycons();
// Listen for theme changes
window.addEventListener('adminator:themeChanged', initSkycons);
}());
================================================
FILE: src/assets/scripts/ui/index.js
================================================
/**
* UI Page Bootstrap Components
* Vanilla JavaScript implementations for Bootstrap components
*/
export default (function () {
// Modal functionality
class VanillaModal {
constructor(element) {
this.element = element;
this.modal = null;
this.backdrop = null;
this.isOpen = false;
this.init();
}
init() {
this.modal = document.querySelector(this.element.getAttribute('data-bs-target'));
if (this.modal) {
this.element.addEventListener('click', (e) => {
e.preventDefault();
this.show();
});
// Close button functionality
const closeButtons = this.modal.querySelectorAll('[data-bs-dismiss="modal"]');
closeButtons.forEach(btn => {
btn.addEventListener('click', () => this.hide());
});
// Close on backdrop click
this.modal.addEventListener('click', (e) => {
if (e.target === this.modal) {
this.hide();
}
});
}
}
show() {
if (this.isOpen) return;
// Create backdrop
this.backdrop = document.createElement('div');
this.backdrop.className = 'modal-backdrop fade show';
document.body.appendChild(this.backdrop);
// Show modal
this.modal.style.display = 'block';
this.modal.classList.add('show');
document.body.classList.add('modal-open');
this.isOpen = true;
// Focus the modal
this.modal.setAttribute('tabindex', '-1');
this.modal.focus();
// Escape key handler
this.escapeHandler = (e) => {
if (e.key === 'Escape') {
this.hide();
}
};
document.addEventListener('keydown', this.escapeHandler);
}
hide() {
if (!this.isOpen) return;
// Hide modal
this.modal.classList.remove('show');
this.modal.style.display = 'none';
document.body.classList.remove('modal-open');
// Remove backdrop
if (this.backdrop) {
this.backdrop.remove();
this.backdrop = null;
}
this.isOpen = false;
// Remove escape handler
if (this.escapeHandler) {
document.removeEventListener('keydown', this.escapeHandler);
this.escapeHandler = null;
}
}
}
// Dropdown functionality
class VanillaDropdown {
constructor(element) {
this.element = element;
this.menu = null;
this.isOpen = false;
this.init();
}
init() {
this.menu = this.element.parentNode.querySelector('.dropdown-menu');
if (this.menu) {
this.element.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
this.toggle();
});
// Close on outside click
document.addEventListener('click', (e) => {
if (!this.element.parentNode.contains(e.target)) {
this.hide();
}
});
// Close on escape
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && this.isOpen) {
this.hide();
}
});
}
}
toggle() {
if (this.isOpen) {
this.hide();
} else {
this.show();
}
}
show() {
if (this.isOpen) return;
// Close other dropdowns
document.querySelectorAll('.dropdown-menu.show').forEach(menu => {
menu.classList.remove('show');
});
this.menu.classList.add('show');
this.element.setAttribute('aria-expanded', 'true');
this.isOpen = true;
}
hide() {
if (!this.isOpen) return;
this.menu.classList.remove('show');
this.element.setAttribute('aria-expanded', 'false');
this.isOpen = false;
}
}
// Popover functionality
class VanillaPopover {
constructor(element) {
this.element = element;
this.popover = null;
this.isOpen = false;
this.init();
}
init() {
this.element.addEventListener('click', (e) => {
e.preventDefault();
this.toggle();
});
// Close on outside click
document.addEventListener('click', (e) => {
if (!this.element.contains(e.target) && (!this.popover || !this.popover.contains(e.target))) {
this.hide();
}
});
}
toggle() {
if (this.isOpen) {
this.hide();
} else {
this.show();
}
}
show() {
if (this.isOpen) return;
// Close other popovers
document.querySelectorAll('.popover').forEach(popover => {
popover.remove();
});
const title = this.element.getAttribute('title') || this.element.getAttribute('data-bs-title');
const content = this.element.getAttribute('data-bs-content');
this.popover = document.createElement('div');
this.popover.className = 'popover bs-popover-top show';
this.popover.style.position = 'absolute';
this.popover.style.zIndex = '1070';
this.popover.style.maxWidth = '276px';
this.popover.style.backgroundColor = '#fff';
this.popover.style.border = '1px solid rgba(0,0,0,.2)';
this.popover.style.borderRadius = '6px';
this.popover.style.boxShadow = '0 5px 10px rgba(0,0,0,.2)';
let popoverContent = '';
if (title) {
popoverContent += ``;
}
popoverContent += `${content}
`;
this.popover.innerHTML = popoverContent;
document.body.appendChild(this.popover);
// Position popover
const rect = this.element.getBoundingClientRect();
this.popover.style.left = `${rect.left + (rect.width / 2) - (this.popover.offsetWidth / 2)}px`;
this.popover.style.top = `${rect.top - this.popover.offsetHeight - 10}px`;
this.isOpen = true;
}
hide() {
if (!this.isOpen) return;
if (this.popover) {
this.popover.remove();
this.popover = null;
}
this.isOpen = false;
}
}
// Tooltip functionality
class VanillaTooltip {
constructor(element) {
this.element = element;
this.tooltip = null;
this.init();
}
init() {
this.element.addEventListener('mouseenter', () => this.show());
this.element.addEventListener('mouseleave', () => this.hide());
this.element.addEventListener('focus', () => this.show());
this.element.addEventListener('blur', () => this.hide());
}
show() {
if (this.tooltip) return;
const title = this.element.getAttribute('title') || this.element.getAttribute('data-bs-title');
const placement = this.element.getAttribute('data-bs-placement') || 'top';
if (!title) return;
this.tooltip = document.createElement('div');
this.tooltip.className = `tooltip bs-tooltip-${placement} show`;
this.tooltip.style.position = 'absolute';
this.tooltip.style.zIndex = '1070';
this.tooltip.style.maxWidth = '200px';
this.tooltip.style.padding = '4px 8px';
this.tooltip.style.fontSize = '12px';
this.tooltip.style.backgroundColor = '#000';
this.tooltip.style.color = '#fff';
this.tooltip.style.borderRadius = '4px';
this.tooltip.style.pointerEvents = 'none';
this.tooltip.style.whiteSpace = 'nowrap';
this.tooltip.innerHTML = `${title}
`;
document.body.appendChild(this.tooltip);
// Position tooltip
const rect = this.element.getBoundingClientRect();
switch (placement) {
case 'top':
this.tooltip.style.left = `${rect.left + (rect.width / 2) - (this.tooltip.offsetWidth / 2)}px`;
this.tooltip.style.top = `${rect.top - this.tooltip.offsetHeight - 5}px`;
break;
case 'bottom':
this.tooltip.style.left = `${rect.left + (rect.width / 2) - (this.tooltip.offsetWidth / 2)}px`;
this.tooltip.style.top = `${rect.bottom + 5}px`;
break;
case 'left':
this.tooltip.style.left = `${rect.left - this.tooltip.offsetWidth - 5}px`;
this.tooltip.style.top = `${rect.top + (rect.height / 2) - (this.tooltip.offsetHeight / 2)}px`;
break;
case 'right':
this.tooltip.style.left = `${rect.right + 5}px`;
this.tooltip.style.top = `${rect.top + (rect.height / 2) - (this.tooltip.offsetHeight / 2)}px`;
break;
}
}
hide() {
if (this.tooltip) {
this.tooltip.remove();
this.tooltip = null;
}
}
}
// Accordion functionality
class VanillaAccordion {
constructor(element) {
this.element = element;
this.accordion = element.closest('.accordion');
this.target = document.querySelector(element.getAttribute('data-bs-target'));
this.isOpen = !element.classList.contains('collapsed');
this.init();
}
init() {
this.element.addEventListener('click', (e) => {
e.preventDefault();
this.toggle();
});
}
toggle() {
if (this.isOpen) {
this.hide();
} else {
this.show();
}
}
show() {
if (this.isOpen) return;
// Close other accordion items in the same parent
const parentAccordion = this.accordion;
if (parentAccordion) {
const otherItems = parentAccordion.querySelectorAll('.accordion-collapse.show');
otherItems.forEach(item => {
if (item !== this.target) {
item.classList.remove('show');
const button = parentAccordion.querySelector(`[data-bs-target="#${item.id}"]`);
if (button) {
button.classList.add('collapsed');
button.setAttribute('aria-expanded', 'false');
}
}
});
}
// Show this item
this.target.classList.add('show');
this.element.classList.remove('collapsed');
this.element.setAttribute('aria-expanded', 'true');
this.isOpen = true;
}
hide() {
if (!this.isOpen) return;
this.target.classList.remove('show');
this.element.classList.add('collapsed');
this.element.setAttribute('aria-expanded', 'false');
this.isOpen = false;
}
}
// Initialize all components
const initComponents = () => {
// Initialize modals
document.querySelectorAll('[data-bs-toggle="modal"]').forEach(element => {
new VanillaModal(element);
});
// Initialize dropdowns
document.querySelectorAll('[data-bs-toggle="dropdown"]').forEach(element => {
new VanillaDropdown(element);
});
// Initialize popovers
document.querySelectorAll('[data-bs-toggle="popover"]').forEach(element => {
new VanillaPopover(element);
});
// Initialize tooltips
document.querySelectorAll('[data-bs-toggle="tooltip"]').forEach(element => {
new VanillaTooltip(element);
});
// Initialize accordions
document.querySelectorAll('[data-bs-toggle="collapse"]').forEach(element => {
new VanillaAccordion(element);
});
};
// Initialize when DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initComponents);
} else {
initComponents();
}
// Public API
return {
init: initComponents,
Modal: VanillaModal,
Dropdown: VanillaDropdown,
Popover: VanillaPopover,
Tooltip: VanillaTooltip,
Accordion: VanillaAccordion,
};
}());
================================================
FILE: src/assets/scripts/utils/date.js
================================================
/**
* Modern Date Utilities
* Using Day.js (2KB) instead of Moment.js (67KB) - 97% size reduction
* Provides consistent date formatting and manipulation across the application
*/
import dayjs from 'dayjs';
import utc from 'dayjs/plugin/utc';
import timezone from 'dayjs/plugin/timezone';
import relativeTime from 'dayjs/plugin/relativeTime';
import customParseFormat from 'dayjs/plugin/customParseFormat';
import advancedFormat from 'dayjs/plugin/advancedFormat';
// Enable Day.js plugins
dayjs.extend(utc);
dayjs.extend(timezone);
dayjs.extend(relativeTime);
dayjs.extend(customParseFormat);
dayjs.extend(advancedFormat);
export const DateUtils = {
/**
* Get current date/time
*/
now: () => dayjs(),
/**
* Parse date from string or Date object
*/
parse: (input, format = null) => {
return format ? dayjs(input, format) : dayjs(input);
},
/**
* Format date for display
*/
format: (date, format = 'YYYY-MM-DD') => {
return dayjs(date).format(format);
},
/**
* Common date formatting presets
*/
formatters: {
// Dashboard display formats
shortDate: (date) => dayjs(date).format('MMM DD, YYYY'),
longDate: (date) => dayjs(date).format('MMMM DD, YYYY'),
dateTime: (date) => dayjs(date).format('MMM DD, YYYY h:mm A'),
// Calendar formats
calendarDate: (date) => dayjs(date).format('YYYY-MM-DD'),
calendarDateTime: (date) => dayjs(date).format('YYYY-MM-DD HH:mm:ss'),
// Form input formats
inputDate: (date) => dayjs(date).format('YYYY-MM-DD'),
inputDateTime: (date) => dayjs(date).format('YYYY-MM-DDTHH:mm'),
// Display formats
timeOnly: (date) => dayjs(date).format('h:mm A'),
monthYear: (date) => dayjs(date).format('MMMM YYYY'),
dayMonth: (date) => dayjs(date).format('DD MMM'),
// Relative time
relative: (date) => dayjs(date).fromNow(),
relativeCalendar: (date) => {
const now = dayjs();
const target = dayjs(date);
const diffDays = now.diff(target, 'day');
if (diffDays === 0) return 'Today';
if (diffDays === 1) return 'Yesterday';
if (diffDays === -1) return 'Tomorrow';
if (diffDays > 1 && diffDays < 7) return `${diffDays} days ago`;
if (diffDays < -1 && diffDays > -7) return `In ${Math.abs(diffDays)} days`;
return target.format('MMM DD, YYYY');
},
},
/**
* Date manipulation
*/
add: (date, amount, unit) => dayjs(date).add(amount, unit),
subtract: (date, amount, unit) => dayjs(date).subtract(amount, unit),
startOf: (date, unit) => dayjs(date).startOf(unit),
endOf: (date, unit) => dayjs(date).endOf(unit),
/**
* Date comparison
*/
isBefore: (date1, date2) => dayjs(date1).isBefore(dayjs(date2)),
isAfter: (date1, date2) => dayjs(date1).isAfter(dayjs(date2)),
isSame: (date1, date2, unit = 'day') => dayjs(date1).isSame(dayjs(date2), unit),
isBetween: (date, start, end) => dayjs(date).isBetween(dayjs(start), dayjs(end)),
/**
* Date validation
*/
isValid: (date) => dayjs(date).isValid(),
/**
* Timezone utilities
*/
timezone: {
convert: (date, tz) => dayjs(date).tz(tz),
utc: (date) => dayjs(date).utc(),
local: (date) => dayjs(date).local(),
guess: () => dayjs.tz.guess(),
},
/**
* Calendar utilities
*/
calendar: {
// Get calendar month data for building calendar views
getMonthData: (date = null) => {
const target = date ? dayjs(date) : dayjs();
const startOfMonth = target.startOf('month');
const endOfMonth = target.endOf('month');
const startOfCalendar = startOfMonth.startOf('week');
const endOfCalendar = endOfMonth.endOf('week');
const days = [];
let current = startOfCalendar;
while (current.isBefore(endOfCalendar) || current.isSame(endOfCalendar, 'day')) {
days.push({
date: current.format('YYYY-MM-DD'),
day: current.date(),
isCurrentMonth: current.isSame(target, 'month'),
isToday: current.isSame(dayjs(), 'day'),
dayjs: current.clone(),
});
current = current.add(1, 'day');
}
return {
month: target.format('MMMM YYYY'),
year: target.year(),
monthIndex: target.month(),
days,
};
},
// Get week data
getWeekData: (date = null) => {
const target = date ? dayjs(date) : dayjs();
const startOfWeek = target.startOf('week');
const endOfWeek = target.endOf('week');
const days = [];
let current = startOfWeek;
while (current.isBefore(endOfWeek) || current.isSame(endOfWeek, 'day')) {
days.push({
date: current.format('YYYY-MM-DD'),
day: current.date(),
dayName: current.format('dddd'),
shortDayName: current.format('ddd'),
isToday: current.isSame(dayjs(), 'day'),
dayjs: current.clone(),
});
current = current.add(1, 'day');
}
return {
weekStart: startOfWeek.format('MMM DD'),
weekEnd: endOfWeek.format('MMM DD, YYYY'),
days,
};
},
},
/**
* Form utilities
*/
form: {
// Convert date to HTML5 input format
toInputValue: (date) => dayjs(date).format('YYYY-MM-DD'),
toDateTimeInputValue: (date) => dayjs(date).format('YYYY-MM-DDTHH:mm'),
// Parse from HTML5 input
fromInputValue: (value) => dayjs(value),
// Validate date input
validateDateInput: (value) => {
const parsed = dayjs(value);
return parsed.isValid() && value.length >= 8; // Basic validation
},
},
/**
* Chart/Data utilities
*/
charts: {
// Generate date ranges for charts
generateDateRange: (start, end, interval = 'day') => {
const dates = [];
let current = dayjs(start);
const endDate = dayjs(end);
while (current.isBefore(endDate) || current.isSame(endDate, interval)) {
dates.push({
date: current.format('YYYY-MM-DD'),
label: current.format('MMM DD'),
value: current.toISOString(),
dayjs: current.clone(),
});
current = current.add(1, interval);
}
return dates;
},
// Get common chart date labels
getChartLabels: (period = 'week') => {
const now = dayjs();
switch (period) {
case 'week':
return Array.from({ length: 7 }, (_, i) =>
now.subtract(6 - i, 'day').format('ddd')
);
case 'month':
return Array.from({ length: 30 }, (_, i) =>
now.subtract(29 - i, 'day').format('DD')
);
case 'year':
return Array.from({ length: 12 }, (_, i) =>
now.subtract(11 - i, 'month').format('MMM')
);
default:
return [];
}
},
},
};
// Export dayjs instance for direct use when needed
export { dayjs };
// Default export
export default DateUtils;
================================================
FILE: src/assets/scripts/utils/dom.js
================================================
/**
* Adminator DOM Utility Functions
* Provides jQuery-like functionality using vanilla JavaScript
*
* @module utils/dom
* @example
* import { DOM } from './utils/dom';
*
* // Select elements
* const button = DOM.select('.my-button');
* const items = DOM.selectAll('.list-item');
*
* // Add event listeners
* DOM.on(button, 'click', () => console.log('Clicked!'));
*
* // Manipulate classes
* DOM.addClass(button, 'active');
* DOM.toggleClass(button, 'loading');
*/
/**
* DOM utility object providing jQuery-like methods
* @namespace
*/
export const DOM = {
/**
* Select a single element matching the selector
* Replaces jQuery's $('selector').first()
*
* @param {string} selector - CSS selector
* @param {Document|Element} [context=document] - Context to search within
* @returns {Element|null} The matched element or null
*
* @example
* const header = DOM.select('.header');
* const navItem = DOM.select('.nav-item', sidebar);
*/
select: (selector, context = document) => {
return context.querySelector(selector);
},
/**
* Select all elements matching the selector
* Replaces jQuery's $('selector')
*
* @param {string} selector - CSS selector
* @param {Document|Element} [context=document] - Context to search within
* @returns {Element[]} Array of matched elements
*
* @example
* const buttons = DOM.selectAll('.btn');
* buttons.forEach(btn => DOM.addClass(btn, 'initialized'));
*/
selectAll: (selector, context = document) => {
return Array.from(context.querySelectorAll(selector));
},
/**
* Check if an element matching the selector exists
*
* @param {string} selector - CSS selector
* @returns {boolean} True if element exists
*
* @example
* if (DOM.exists('.sidebar')) {
* initSidebar();
* }
*/
exists: (selector) => {
return document.querySelector(selector) !== null;
},
/**
* Add an event listener to an element
* Replaces jQuery's $.on()
*
* @param {Element|string} element - Element or selector
* @param {string} event - Event name (e.g., 'click', 'change')
* @param {Function} handler - Event handler function
* @param {Object} [options={}] - addEventListener options
*
* @example
* DOM.on('.btn', 'click', handleClick);
* DOM.on(button, 'click', handleClick, { once: true });
*/
on: (element, event, handler, options = {}) => {
if (typeof element === 'string') {
element = document.querySelector(element);
}
if (element) {
element.addEventListener(event, handler, options);
}
},
/**
* Remove an event listener from an element
* Replaces jQuery's $.off()
*
* @param {Element|string} element - Element or selector
* @param {string} event - Event name
* @param {Function} handler - Event handler to remove
*
* @example
* DOM.off(button, 'click', handleClick);
*/
off: (element, event, handler) => {
if (typeof element === 'string') {
element = document.querySelector(element);
}
if (element) {
element.removeEventListener(event, handler);
}
},
/**
* Add a class to an element
* Replaces jQuery's $.addClass()
*
* @param {Element|string} element - Element or selector
* @param {string} className - Class name to add
*
* @example
* DOM.addClass('.menu', 'open');
*/
addClass: (element, className) => {
if (typeof element === 'string') {
element = document.querySelector(element);
}
if (element) {
element.classList.add(className);
}
},
/**
* Remove a class from an element
* Replaces jQuery's $.removeClass()
*
* @param {Element|string} element - Element or selector
* @param {string} className - Class name to remove
*
* @example
* DOM.removeClass('.menu', 'open');
*/
removeClass: (element, className) => {
if (typeof element === 'string') {
element = document.querySelector(element);
}
if (element) {
element.classList.remove(className);
}
},
/**
* Toggle a class on an element
* Replaces jQuery's $.toggleClass()
*
* @param {Element|string} element - Element or selector
* @param {string} className - Class name to toggle
*
* @example
* DOM.toggleClass('.dropdown', 'show');
*/
toggleClass: (element, className) => {
if (typeof element === 'string') {
element = document.querySelector(element);
}
if (element) {
element.classList.toggle(className);
}
},
/**
* Check if an element has a class
* Replaces jQuery's $.hasClass()
*
* @param {Element|string} element - Element or selector
* @param {string} className - Class name to check
* @returns {boolean} True if element has the class
*
* @example
* if (DOM.hasClass('.menu', 'open')) {
* closeMenu();
* }
*/
hasClass: (element, className) => {
if (typeof element === 'string') {
element = document.querySelector(element);
}
return element ? element.classList.contains(className) : false;
},
/**
* Get or set an attribute on an element
* Replaces jQuery's $.attr()
*
* @param {Element|string} element - Element or selector
* @param {string} name - Attribute name
* @param {string} [value] - Value to set (omit to get)
* @returns {string|Element|null} Attribute value when getting, element when setting
*
* @example
* // Get attribute
* const href = DOM.attr(link, 'href');
*
* // Set attribute
* DOM.attr(link, 'href', '/new-page');
*/
attr: (element, name, value) => {
if (typeof element === 'string') {
element = document.querySelector(element);
}
if (!element) return null;
if (value === undefined) {
return element.getAttribute(name);
} else {
element.setAttribute(name, value);
return element;
}
},
/**
* Get or set a data attribute on an element
* Replaces jQuery's $.data()
*
* @param {Element|string} element - Element or selector
* @param {string} name - Data attribute name (without 'data-' prefix)
* @param {string} [value] - Value to set (omit to get)
* @returns {string|Element|null} Data value when getting, element when setting
*
* @example
* // Get data attribute
* const id = DOM.data(row, 'id'); // Gets data-id
*
* // Set data attribute
* DOM.data(row, 'id', '123');
*/
data: (element, name, value) => {
if (typeof element === 'string') {
element = document.querySelector(element);
}
if (!element) return null;
const dataName = `data-${name}`;
if (value === undefined) {
return element.getAttribute(dataName);
} else {
element.setAttribute(dataName, value);
return element;
}
},
/**
* Get or set text content of an element
* Replaces jQuery's $.text()
*
* @param {Element|string} element - Element or selector
* @param {string} [content] - Text to set (omit to get)
* @returns {string|Element|null} Text content when getting, element when setting
*
* @example
* const text = DOM.text('.title');
* DOM.text('.title', 'New Title');
*/
text: (element, content) => {
if (typeof element === 'string') {
element = document.querySelector(element);
}
if (!element) return null;
if (content === undefined) {
return element.textContent;
} else {
element.textContent = content;
return element;
}
},
/**
* Get or set HTML content of an element
* Replaces jQuery's $.html()
*
* @param {Element|string} element - Element or selector
* @param {string} [content] - HTML to set (omit to get)
* @returns {string|Element|null} HTML content when getting, element when setting
*
* @example
* const html = DOM.html('.container');
* DOM.html('.container', 'New content
');
*/
html: (element, content) => {
if (typeof element === 'string') {
element = document.querySelector(element);
}
if (!element) return null;
if (content === undefined) {
return element.innerHTML;
} else {
element.innerHTML = content;
return element;
}
},
/**
* Hide an element
* Replaces jQuery's $.hide()
*
* @param {Element|string} element - Element or selector
*
* @example
* DOM.hide('.modal');
*/
hide: (element) => {
if (typeof element === 'string') {
element = document.querySelector(element);
}
if (element) {
element.style.display = 'none';
}
},
/**
* Show an element
* Replaces jQuery's $.show()
*
* @param {Element|string} element - Element or selector
* @param {string} [display='block'] - Display value to use
*
* @example
* DOM.show('.modal');
* DOM.show('.flex-item', 'flex');
*/
show: (element, display = 'block') => {
if (typeof element === 'string') {
element = document.querySelector(element);
}
if (element) {
element.style.display = display;
}
},
/**
* Toggle element visibility
* Replaces jQuery's $.toggle()
*
* @param {Element|string} element - Element or selector
* @param {string} [display='block'] - Display value when showing
*
* @example
* DOM.toggle('.menu');
*/
toggle: (element, display = 'block') => {
if (typeof element === 'string') {
element = document.querySelector(element);
}
if (element) {
if (element.style.display === 'none') {
element.style.display = display;
} else {
element.style.display = 'none';
}
}
},
/**
* Animate element sliding up (collapsing)
* Replaces jQuery's $.slideUp()
*
* @param {Element|string} element - Element or selector
* @param {number} [duration=300] - Animation duration in ms
* @returns {Promise} Resolves when animation completes
*
* @example
* await DOM.slideUp('.panel');
*/
slideUp: (element, duration = 300) => {
if (typeof element === 'string') {
element = document.querySelector(element);
}
if (!element) return Promise.resolve();
return new Promise((resolve) => {
const height = element.scrollHeight;
element.style.height = `${height}px`;
element.style.overflow = 'hidden';
element.animate([
{ height: `${height}px` },
{ height: '0px' },
], {
duration,
easing: 'ease-in-out',
}).onfinish = () => {
element.style.display = 'none';
element.style.height = '';
element.style.overflow = '';
resolve();
};
});
},
/**
* Animate element sliding down (expanding)
* Replaces jQuery's $.slideDown()
*
* @param {Element|string} element - Element or selector
* @param {number} [duration=300] - Animation duration in ms
* @returns {Promise} Resolves when animation completes
*
* @example
* await DOM.slideDown('.panel');
*/
slideDown: (element, duration = 300) => {
if (typeof element === 'string') {
element = document.querySelector(element);
}
if (!element) return Promise.resolve();
return new Promise((resolve) => {
element.style.display = 'block';
element.style.height = '0px';
element.style.overflow = 'hidden';
const height = element.scrollHeight;
element.animate([
{ height: '0px' },
{ height: `${height}px` },
], {
duration,
easing: 'ease-in-out',
}).onfinish = () => {
element.style.height = 'auto';
element.style.overflow = 'visible';
resolve();
};
});
},
/**
* Animate element fading in
* Replaces jQuery's $.fadeIn()
*
* @param {Element|string} element - Element or selector
* @param {number} [duration=300] - Animation duration in ms
* @returns {Promise} Resolves when animation completes
*
* @example
* await DOM.fadeIn('.modal');
*/
fadeIn: (element, duration = 300) => {
if (typeof element === 'string') {
element = document.querySelector(element);
}
if (!element) return Promise.resolve();
return new Promise((resolve) => {
element.style.opacity = '0';
element.style.display = 'block';
element.animate([
{ opacity: 0 },
{ opacity: 1 },
], {
duration,
easing: 'ease-in-out',
}).onfinish = () => {
element.style.opacity = '';
resolve();
};
});
},
/**
* Animate element fading out
* Replaces jQuery's $.fadeOut()
*
* @param {Element|string} element - Element or selector
* @param {number} [duration=300] - Animation duration in ms
* @returns {Promise} Resolves when animation completes
*
* @example
* await DOM.fadeOut('.modal');
*/
fadeOut: (element, duration = 300) => {
if (typeof element === 'string') {
element = document.querySelector(element);
}
if (!element) return Promise.resolve();
return new Promise((resolve) => {
element.animate([
{ opacity: 1 },
{ opacity: 0 },
], {
duration,
easing: 'ease-in-out',
}).onfinish = () => {
element.style.display = 'none';
element.style.opacity = '';
resolve();
};
});
},
/**
* Get element dimensions and position relative to viewport
*
* @param {Element|string} element - Element or selector
* @returns {Object|null} Dimensions object or null
* @property {number} width - Element width
* @property {number} height - Element height
* @property {number} top - Distance from viewport top
* @property {number} left - Distance from viewport left
* @property {number} bottom - Distance from viewport bottom
* @property {number} right - Distance from viewport right
*
* @example
* const { width, height, top, left } = DOM.dimensions('.card');
*/
dimensions: (element) => {
if (typeof element === 'string') {
element = document.querySelector(element);
}
if (!element) return null;
const rect = element.getBoundingClientRect();
return {
width: rect.width,
height: rect.height,
top: rect.top,
left: rect.left,
bottom: rect.bottom,
right: rect.right,
};
},
/**
* Execute callback when DOM is ready
* Replaces jQuery's $(document).ready()
*
* @param {Function} callback - Function to execute when DOM is ready
*
* @example
* DOM.ready(() => {
* initApp();
* });
*/
ready: (callback) => {
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', callback);
} else {
callback();
}
},
/**
* Create an element with optional attributes and children
*
* @param {string} tag - HTML tag name
* @param {Object} [attrs={}] - Attributes to set
* @param {Array} [children=[]] - Child elements or text
* @returns {Element} The created element
*
* @example
* const button = DOM.create('button', { class: 'btn', type: 'submit' }, ['Submit']);
*/
create: (tag, attrs = {}, children = []) => {
const el = document.createElement(tag);
Object.entries(attrs).forEach(([key, value]) => {
if (key === 'class') {
el.className = value;
} else if (key.startsWith('data-')) {
el.setAttribute(key, value);
} else {
el[key] = value;
}
});
children.forEach(child => {
if (typeof child === 'string') {
el.appendChild(document.createTextNode(child));
} else if (child instanceof Element) {
el.appendChild(child);
}
});
return el;
},
/**
* Find the closest ancestor matching a selector
*
* @param {Element|string} element - Element or selector
* @param {string} selector - Selector to match
* @returns {Element|null} Closest matching ancestor or null
*
* @example
* const form = DOM.closest(input, 'form');
*/
closest: (element, selector) => {
if (typeof element === 'string') {
element = document.querySelector(element);
}
return element ? element.closest(selector) : null;
},
};
export default DOM;
================================================
FILE: src/assets/scripts/utils/events.js
================================================
/**
* Adminator Event Utilities
* Provides efficient event handling with delegation and cleanup
*
* @module utils/events
*/
/**
* Store for event handlers to enable proper cleanup
* @type {WeakMap>>}
*/
const handlerRegistry = new WeakMap();
/**
* Store for AbortControllers to enable easy cleanup
* @type {WeakMap}
*/
const controllerRegistry = new WeakMap();
/**
* Event utilities namespace
* @namespace
*/
const Events = {
/**
* Add an event listener with automatic cleanup support
* Uses AbortController for efficient removal
*
* @param {Element} element - Target element
* @param {string} event - Event type
* @param {Function} handler - Event handler
* @param {Object} [options={}] - Event listener options
* @returns {Function} Cleanup function to remove the listener
*
* @example
* const cleanup = Events.on(button, 'click', handleClick);
* // Later: cleanup() to remove
*/
on(element, event, handler, options = {}) {
if (!element) return () => {};
// Get or create AbortController for this element
let controller = controllerRegistry.get(element);
if (!controller) {
controller = new AbortController();
controllerRegistry.set(element, controller);
}
// Register handler
if (!handlerRegistry.has(element)) {
handlerRegistry.set(element, new Map());
}
const elementHandlers = handlerRegistry.get(element);
if (!elementHandlers.has(event)) {
elementHandlers.set(event, new Set());
}
elementHandlers.get(event).add(handler);
// Add listener with abort signal
element.addEventListener(event, handler, {
...options,
signal: controller.signal,
});
// Return cleanup function
return () => {
element.removeEventListener(event, handler, options);
const handlers = handlerRegistry.get(element)?.get(event);
if (handlers) {
handlers.delete(handler);
}
};
},
/**
* Remove all event listeners from an element
*
* @param {Element} element - Target element
*
* @example
* Events.off(element); // Removes all listeners
*/
off(element) {
if (!element) return;
const controller = controllerRegistry.get(element);
if (controller) {
controller.abort();
controllerRegistry.delete(element);
}
handlerRegistry.delete(element);
},
/**
* Add event delegation - listen on parent for events from children
* More efficient than adding listeners to many elements
*
* @param {Element} parent - Parent element to listen on
* @param {string} event - Event type
* @param {string} selector - CSS selector for target elements
* @param {Function} handler - Event handler (receives event and matched element)
* @param {Object} [options={}] - Event listener options
* @returns {Function} Cleanup function
*
* @example
* // Instead of adding click to every .btn
* Events.delegate(container, 'click', '.btn', (e, btn) => {
* console.log('Button clicked:', btn);
* });
*/
delegate(parent, event, selector, handler, options = {}) {
if (!parent) return () => {};
const delegatedHandler = (e) => {
const target = e.target.closest(selector);
if (target && parent.contains(target)) {
handler.call(target, e, target);
}
};
return this.on(parent, event, delegatedHandler, options);
},
/**
* Add a one-time event listener
*
* @param {Element} element - Target element
* @param {string} event - Event type
* @param {Function} handler - Event handler
* @returns {Function} Cleanup function
*
* @example
* Events.once(button, 'click', handleFirstClick);
*/
once(element, event, handler) {
return this.on(element, event, handler, { once: true });
},
/**
* Create a debounced event handler
*
* @param {Function} handler - Original handler
* @param {number} [delay=250] - Debounce delay in ms
* @returns {Function} Debounced handler
*
* @example
* const debouncedResize = Events.debounce(handleResize, 200);
* window.addEventListener('resize', debouncedResize);
*/
debounce(handler, delay = 250) {
let timeoutId;
return function debounced(...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => handler.apply(this, args), delay);
};
},
/**
* Create a throttled event handler
*
* @param {Function} handler - Original handler
* @param {number} [limit=250] - Throttle limit in ms
* @returns {Function} Throttled handler
*
* @example
* const throttledScroll = Events.throttle(handleScroll, 100);
* window.addEventListener('scroll', throttledScroll);
*/
throttle(handler, limit = 250) {
let inThrottle;
return function throttled(...args) {
if (!inThrottle) {
handler.apply(this, args);
inThrottle = true;
setTimeout(() => (inThrottle = false), limit);
}
};
},
/**
* Dispatch a custom event
*
* @param {Element|Window} target - Target to dispatch on
* @param {string} eventName - Event name
* @param {Object} [detail={}] - Event detail data
* @param {Object} [options={}] - Event options (bubbles, cancelable)
* @returns {boolean} Whether the event was not cancelled
*
* @example
* Events.emit(element, 'custom:event', { data: 'value' });
*/
emit(target, eventName, detail = {}, options = {}) {
const event = new CustomEvent(eventName, {
detail,
bubbles: options.bubbles ?? true,
cancelable: options.cancelable ?? true,
});
return target.dispatchEvent(event);
},
};
export default Events;
================================================
FILE: src/assets/scripts/utils/index.js
================================================
/**
* Adminator Utilities Index
* Re-exports all utility modules for convenient importing
*
* @module utils
*/
// Re-export all utilities
export { default as DOM } from './dom';
export { default as Theme } from './theme';
export { default as DateUtils } from './date';
export { default as Logger } from './logger';
export { default as Events } from './events';
export { default as Performance } from './performance';
export { default as Storage } from './storage';
export { default as Sanitize } from './sanitize';
// Legacy initialization code
export default (function () {
// ------------------------------------------------------
// @Window Resize
// ------------------------------------------------------
/**
* NOTE: Register resize event for Masonry layout
*/
const EVENT = document.createEvent('UIEvents');
window.EVENT = EVENT;
EVENT.initUIEvent('resize', true, false, window, 0);
window.addEventListener('load', () => {
/**
* Trigger window resize event after page load
* for recalculation of masonry layout.
*/
window.dispatchEvent(EVENT);
});
// ------------------------------------------------------
// @External Links
// ------------------------------------------------------
// Open external links in new window
const externalLinks = document.querySelectorAll('a[href^="http"], a[href^="//"]');
externalLinks.forEach(link => {
const href = link.getAttribute('href');
if (href && !href.includes(window.location.host)) {
link.setAttribute('rel', 'noopener noreferrer');
link.setAttribute('target', '_blank');
}
});
// ------------------------------------------------------
// @Resize Trigger
// ------------------------------------------------------
// Trigger resize on any element click
document.addEventListener('click', () => {
window.dispatchEvent(window.EVENT);
});
}());
================================================
FILE: src/assets/scripts/utils/logger.js
================================================
/**
* Adminator Logger Utility
* Development-only logging utility for debugging
*
* @module utils/logger
*/
/**
* Check if we're in development mode
* @returns {boolean}
*/
const isDev = () => {
try {
return process.env.NODE_ENV === 'development' ||
window.location.hostname === 'localhost' ||
window.location.hostname === '127.0.0.1';
} catch {
return false;
}
};
/**
* Logger object with development-only output
* All methods are no-ops in production for zero overhead
*/
const Logger = {
/**
* Log informational messages (development only)
* @param {string} message - The message to log
* @param {Object} [context] - Additional context data
*/
info(message, context) {
if (isDev()) {
console.info(`[Adminator] ${message}`, context || '');
}
},
/**
* Log warning messages (development only)
* @param {string} message - The warning message
* @param {Object} [context] - Additional context data
*/
warn(message, context) {
if (isDev()) {
console.warn(`[Adminator] ${message}`, context || '');
}
},
/**
* Log error messages (development only)
* @param {string} message - The error message
* @param {Error|Object} [context] - Error object or context data
*/
error(message, context) {
if (isDev()) {
console.error(`[Adminator] ${message}`, context || '');
}
},
/**
* Log debug messages (development only)
* @param {string} message - The debug message
* @param {Object} [context] - Additional context data
*/
debug(message, context) {
if (isDev()) {
console.debug(`[Adminator] ${message}`, context || '');
}
},
/**
* Group related log messages (development only)
* @param {string} label - Group label
*/
group(label) {
if (isDev()) {
console.group(`[Adminator] ${label}`);
}
},
/**
* End a log group (development only)
*/
groupEnd() {
if (isDev()) {
console.groupEnd();
}
},
/**
* Log with timing information (development only)
* @param {string} label - Timer label
*/
time(label) {
if (isDev()) {
console.time(`[Adminator] ${label}`);
}
},
/**
* End timing and log result (development only)
* @param {string} label - Timer label (must match time() call)
*/
timeEnd(label) {
if (isDev()) {
console.timeEnd(`[Adminator] ${label}`);
}
},
/**
* Log a table of data (development only)
* @param {Array|Object} data - Data to display as table
*/
table(data) {
if (isDev()) {
console.table(data);
}
},
};
export default Logger;
================================================
FILE: src/assets/scripts/utils/performance.js
================================================
/**
* Adminator Performance Utilities
* Provides ResizeObserver, IntersectionObserver, and lazy loading utilities
*
* @module utils/performance
*/
/**
* Store for ResizeObserver instances
* @type {WeakMap}
*/
const resizeCallbacks = new WeakMap();
/**
* Shared ResizeObserver instance for efficiency
* @type {ResizeObserver|null}
*/
let sharedResizeObserver = null;
/**
* Store for IntersectionObserver callbacks
* @type {WeakMap}
*/
const intersectionCallbacks = new WeakMap();
/**
* Map of IntersectionObservers by threshold
* @type {Map}
*/
const intersectionObservers = new Map();
/**
* Performance utilities namespace
* @namespace
*/
const Performance = {
/**
* Observe element resize events efficiently
* Uses shared ResizeObserver for better performance
*
* @param {Element} element - Element to observe
* @param {Function} callback - Callback receiving { width, height, entry }
* @returns {Function} Cleanup function to stop observing
*
* @example
* const unobserve = Performance.onResize(chart, ({ width, height }) => {
* chart.resize(width, height);
* });
*/
onResize(element, callback) {
if (!element || typeof callback !== 'function') {
return () => {};
}
// Create shared observer if needed
if (!sharedResizeObserver) {
sharedResizeObserver = new ResizeObserver((entries) => {
for (const entry of entries) {
const cb = resizeCallbacks.get(entry.target);
if (cb) {
const { width, height } = entry.contentRect;
cb({ width, height, entry });
}
}
});
}
// Store callback and observe
resizeCallbacks.set(element, callback);
sharedResizeObserver.observe(element);
// Return cleanup function
return () => {
resizeCallbacks.delete(element);
sharedResizeObserver?.unobserve(element);
};
},
/**
* Observe when element enters/exits viewport
* Useful for lazy loading and animations
*
* @param {Element} element - Element to observe
* @param {Function} callback - Callback receiving { isIntersecting, entry }
* @param {Object} [options={}] - IntersectionObserver options
* @param {number} [options.threshold=0] - Visibility threshold (0-1)
* @param {string} [options.rootMargin='0px'] - Root margin
* @returns {Function} Cleanup function to stop observing
*
* @example
* const unobserve = Performance.onVisible(element, ({ isIntersecting }) => {
* if (isIntersecting) {
* loadContent();
* unobserve(); // Stop after first trigger
* }
* });
*/
onVisible(element, callback, options = {}) {
if (!element || typeof callback !== 'function') {
return () => {};
}
const { threshold = 0, rootMargin = '0px' } = options;
const key = `${threshold}-${rootMargin}`;
// Get or create observer for this threshold
if (!intersectionObservers.has(key)) {
const observer = new IntersectionObserver(
(entries) => {
for (const entry of entries) {
const cb = intersectionCallbacks.get(entry.target);
if (cb) {
cb({
isIntersecting: entry.isIntersecting,
ratio: entry.intersectionRatio,
entry,
});
}
}
},
{ threshold, rootMargin }
);
intersectionObservers.set(key, observer);
}
const observer = intersectionObservers.get(key);
// Store callback and observe
intersectionCallbacks.set(element, callback);
observer.observe(element);
// Return cleanup function
return () => {
intersectionCallbacks.delete(element);
observer.unobserve(element);
};
},
/**
* Lazy load an element when it becomes visible
* Automatically handles cleanup after loading
*
* @param {Element} element - Element to lazy load
* @param {Function} loadFn - Function to call when visible
* @param {Object} [options={}] - Options
* @param {string} [options.rootMargin='100px'] - Preload margin
* @returns {Function} Cleanup function
*
* @example
* Performance.lazyLoad(chartContainer, () => {
* initializeChart(chartContainer);
* });
*/
lazyLoad(element, loadFn, options = {}) {
const { rootMargin = '100px' } = options;
let loaded = false;
const unobserve = this.onVisible(
element,
({ isIntersecting }) => {
if (isIntersecting && !loaded) {
loaded = true;
loadFn();
unobserve();
}
},
{ rootMargin }
);
return unobserve;
},
/**
* Batch DOM reads and writes to prevent layout thrashing
*
* @param {Function} readFn - Function that reads from DOM
* @param {Function} writeFn - Function that writes to DOM
*
* @example
* Performance.batch(
* () => element.offsetHeight, // Read
* (height) => element.style.minHeight = height + 'px' // Write
* );
*/
batch(readFn, writeFn) {
// Use requestAnimationFrame for batching
requestAnimationFrame(() => {
const value = readFn();
requestAnimationFrame(() => {
writeFn(value);
});
});
},
/**
* Execute callback on next animation frame
*
* @param {Function} callback - Function to execute
* @returns {number} Request ID for cancellation
*
* @example
* const id = Performance.nextFrame(() => updateUI());
* // Cancel: cancelAnimationFrame(id);
*/
nextFrame(callback) {
return requestAnimationFrame(callback);
},
/**
* Execute callback when browser is idle
* Falls back to setTimeout if requestIdleCallback not available
*
* @param {Function} callback - Function to execute
* @param {Object} [options={}] - Options
* @param {number} [options.timeout=1000] - Maximum wait time
* @returns {number} Request ID for cancellation
*
* @example
* Performance.whenIdle(() => {
* // Non-critical work
* preloadNextPage();
* });
*/
whenIdle(callback, options = {}) {
const { timeout = 1000 } = options;
if ('requestIdleCallback' in window) {
return requestIdleCallback(callback, { timeout });
}
// Fallback for Safari
return setTimeout(callback, 1);
},
/**
* Cancel an idle callback
*
* @param {number} id - Request ID from whenIdle
*/
cancelIdle(id) {
if ('cancelIdleCallback' in window) {
cancelIdleCallback(id);
} else {
clearTimeout(id);
}
},
/**
* Preload a resource (image, script, etc.)
*
* @param {string} url - URL to preload
* @param {string} [as='image'] - Resource type (image, script, style, font)
* @returns {Promise} Resolves when loaded
*
* @example
* await Performance.preload('/images/hero.jpg', 'image');
*/
preload(url, as = 'image') {
return new Promise((resolve, reject) => {
const link = document.createElement('link');
link.rel = 'preload';
link.as = as;
link.href = url;
link.onload = resolve;
link.onerror = reject;
document.head.appendChild(link);
});
},
/**
* Measure execution time of a function
*
* @param {string} label - Label for the measurement
* @param {Function} fn - Function to measure
* @returns {*} Return value of the function
*
* @example
* const result = Performance.measure('render', () => renderChart());
*/
measure(label, fn) {
const start = performance.now();
const result = fn();
const end = performance.now();
if (process.env.NODE_ENV === 'development') {
console.log(`[Adminator] ${label}: ${(end - start).toFixed(2)}ms`);
}
return result;
},
/**
* Cleanup all observers
* Call this when destroying the app
*/
cleanup() {
// Cleanup resize observer
if (sharedResizeObserver) {
sharedResizeObserver.disconnect();
sharedResizeObserver = null;
}
// Cleanup intersection observers
for (const observer of intersectionObservers.values()) {
observer.disconnect();
}
intersectionObservers.clear();
},
};
export default Performance;
================================================
FILE: src/assets/scripts/utils/sanitize.js
================================================
/**
* Adminator Sanitization Utilities
* HTML and input sanitization for security
*
* @module utils/sanitize
*/
/**
* HTML entities map for encoding
* @type {Object}
*/
const HTML_ENTITIES = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": ''',
'/': '/',
'`': '`',
'=': '=',
};
/**
* Sanitization utilities namespace
* @namespace
*/
const Sanitize = {
/**
* Escape HTML entities to prevent XSS
*
* @param {string} str - String to escape
* @returns {string} Escaped string safe for HTML insertion
*
* @example
* const safe = Sanitize.html('');
* // Returns: <script>alert("xss")</script>
*/
html(str) {
if (typeof str !== 'string') {
return String(str ?? '');
}
return str.replace(/[&<>"'`=/]/g, char => HTML_ENTITIES[char]);
},
/**
* Escape string for use in HTML attributes
*
* @param {string} str - String to escape
* @returns {string} Escaped string safe for attribute values
*
* @example
* element.setAttribute('data-name', Sanitize.attr(userInput));
*/
attr(str) {
return this.html(str);
},
/**
* Sanitize URL to prevent javascript: and data: URLs
*
* @param {string} url - URL to sanitize
* @param {string[]} [allowedProtocols=['http:', 'https:', 'mailto:', 'tel:']] - Allowed protocols
* @returns {string} Sanitized URL or empty string if invalid
*
* @example
* const safeUrl = Sanitize.url(userProvidedUrl);
* if (safeUrl) {
* window.location.href = safeUrl;
* }
*/
url(url, allowedProtocols = ['http:', 'https:', 'mailto:', 'tel:']) {
if (typeof url !== 'string') {
return '';
}
// Trim and normalize
const trimmed = url.trim().toLowerCase();
// Block dangerous protocols
const dangerousProtocols = ['javascript:', 'data:', 'vbscript:'];
for (const protocol of dangerousProtocols) {
if (trimmed.startsWith(protocol)) {
return '';
}
}
// Check for allowed protocols
try {
const parsed = new URL(url, window.location.origin);
if (!allowedProtocols.includes(parsed.protocol)) {
// Allow relative URLs
if (!url.startsWith('/') && !url.startsWith('./') && !url.startsWith('../')) {
return '';
}
}
return url;
} catch {
// If URL parsing fails, check if it's a relative URL
if (url.startsWith('/') || url.startsWith('./') || url.startsWith('../') || url.startsWith('#')) {
return url;
}
return '';
}
},
/**
* Strip HTML tags from a string
*
* @param {string} str - String with HTML
* @returns {string} String with HTML tags removed
*
* @example
* const text = Sanitize.stripTags('Hello World
');
* // Returns: 'Hello World'
*/
stripTags(str) {
if (typeof str !== 'string') {
return String(str ?? '');
}
// Create a temporary element to leverage browser's HTML parser
const div = document.createElement('div');
div.innerHTML = str;
return div.textContent || div.innerText || '';
},
/**
* Sanitize a string for use in CSS
*
* @param {string} str - String to sanitize
* @returns {string} CSS-safe string
*
* @example
* element.style.setProperty('--custom', Sanitize.css(userInput));
*/
css(str) {
if (typeof str !== 'string') {
return '';
}
// Remove potentially dangerous CSS values
return str
.replace(/expression\s*\(/gi, '')
.replace(/url\s*\(/gi, '')
.replace(/javascript:/gi, '')
.replace(/[<>"']/g, '');
},
/**
* Sanitize a string for use in a filename
*
* @param {string} str - String to sanitize
* @returns {string} Filename-safe string
*
* @example
* const filename = Sanitize.filename(userInput) + '.txt';
*/
filename(str) {
if (typeof str !== 'string') {
return '';
}
return str
.replace(/[/\\?%*:|"<>]/g, '-') // Replace dangerous characters
.replace(/\.\./g, '-') // Prevent directory traversal
.replace(/^\./, '_') // Don't start with dot
.slice(0, 255); // Limit length
},
/**
* Create safe innerHTML by escaping interpolated values
*
* @param {TemplateStringsArray} strings - Template literal strings
* @param {...*} values - Values to interpolate
* @returns {string} HTML string with escaped values
*
* @example
* element.innerHTML = Sanitize.template`${userInput}
`;
*/
template(strings, ...values) {
return strings.reduce((result, str, i) => {
const value = values[i - 1];
const escaped = value !== undefined ? this.html(String(value)) : '';
return result + escaped + str;
});
},
/**
* Validate and sanitize an email address
*
* @param {string} email - Email to validate
* @returns {string} Sanitized email or empty string if invalid
*
* @example
* const email = Sanitize.email(userInput);
* if (email) {
* sendEmail(email);
* }
*/
email(email) {
if (typeof email !== 'string') {
return '';
}
const trimmed = email.trim().toLowerCase();
// Basic email validation
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(trimmed)) {
return '';
}
// Additional checks
if (trimmed.length > 254) {
return '';
}
return trimmed;
},
/**
* Sanitize a number input
*
* @param {*} value - Value to sanitize
* @param {Object} [options={}] - Options
* @param {number} [options.min=-Infinity] - Minimum value
* @param {number} [options.max=Infinity] - Maximum value
* @param {number} [options.default=0] - Default if invalid
* @returns {number} Sanitized number
*
* @example
* const age = Sanitize.number(userInput, { min: 0, max: 120, default: 18 });
*/
number(value, options = {}) {
const { min = -Infinity, max = Infinity, default: defaultValue = 0 } = options;
const num = parseFloat(value);
if (isNaN(num) || !isFinite(num)) {
return defaultValue;
}
return Math.max(min, Math.min(max, num));
},
/**
* Sanitize an integer input
*
* @param {*} value - Value to sanitize
* @param {Object} [options={}] - Options
* @param {number} [options.min=-Infinity] - Minimum value
* @param {number} [options.max=Infinity] - Maximum value
* @param {number} [options.default=0] - Default if invalid
* @returns {number} Sanitized integer
*
* @example
* const count = Sanitize.integer(userInput, { min: 1, max: 100 });
*/
integer(value, options = {}) {
return Math.floor(this.number(value, options));
},
};
export default Sanitize;
================================================
FILE: src/assets/scripts/utils/storage.js
================================================
/**
* Adminator Storage Utilities
* Safe localStorage wrapper with error handling
*
* @module utils/storage
*/
/**
* Check if localStorage is available
* @returns {boolean}
*/
const isAvailable = () => {
try {
const test = '__storage_test__';
localStorage.setItem(test, test);
localStorage.removeItem(test);
return true;
} catch {
return false;
}
};
/**
* In-memory fallback when localStorage is unavailable
* @type {Map}
*/
const memoryStorage = new Map();
/**
* Storage utility with safe localStorage access
* Falls back to in-memory storage when localStorage is unavailable
* (e.g., private browsing, storage quota exceeded)
*
* @namespace
*/
const Storage = {
/**
* Check if storage is available
* @returns {boolean}
*/
isAvailable,
/**
* Get an item from storage
*
* @param {string} key - Storage key
* @returns {string|null} Stored value or null
*
* @example
* const theme = Storage.get('theme');
*/
get(key) {
try {
if (isAvailable()) {
return localStorage.getItem(key);
}
return memoryStorage.get(key) ?? null;
} catch {
return memoryStorage.get(key) ?? null;
}
},
/**
* Set an item in storage
*
* @param {string} key - Storage key
* @param {string} value - Value to store
* @returns {boolean} Success status
*
* @example
* Storage.set('theme', 'dark');
*/
set(key, value) {
try {
if (isAvailable()) {
localStorage.setItem(key, value);
return true;
}
memoryStorage.set(key, value);
return true;
} catch {
// Fallback to memory storage
memoryStorage.set(key, value);
return true;
}
},
/**
* Remove an item from storage
*
* @param {string} key - Storage key
* @returns {boolean} Success status
*
* @example
* Storage.remove('theme');
*/
remove(key) {
try {
if (isAvailable()) {
localStorage.removeItem(key);
}
memoryStorage.delete(key);
return true;
} catch {
memoryStorage.delete(key);
return true;
}
},
/**
* Clear all storage
*
* @returns {boolean} Success status
*
* @example
* Storage.clear();
*/
clear() {
try {
if (isAvailable()) {
localStorage.clear();
}
memoryStorage.clear();
return true;
} catch {
memoryStorage.clear();
return true;
}
},
/**
* Get a JSON object from storage
*
* @param {string} key - Storage key
* @param {*} [defaultValue=null] - Default value if key doesn't exist or parse fails
* @returns {*} Parsed object or default value
*
* @example
* const settings = Storage.getJSON('settings', { theme: 'light' });
*/
getJSON(key, defaultValue = null) {
try {
const value = this.get(key);
if (value === null) {
return defaultValue;
}
return JSON.parse(value);
} catch {
return defaultValue;
}
},
/**
* Set a JSON object in storage
*
* @param {string} key - Storage key
* @param {*} value - Value to store (will be JSON stringified)
* @returns {boolean} Success status
*
* @example
* Storage.setJSON('settings', { theme: 'dark', sidebar: 'expanded' });
*/
setJSON(key, value) {
try {
return this.set(key, JSON.stringify(value));
} catch {
return false;
}
},
/**
* Check if a key exists in storage
*
* @param {string} key - Storage key
* @returns {boolean}
*
* @example
* if (Storage.has('theme')) {
* // Use stored theme
* }
*/
has(key) {
return this.get(key) !== null;
},
/**
* Get all keys in storage
*
* @returns {string[]} Array of keys
*
* @example
* const keys = Storage.keys();
*/
keys() {
try {
if (isAvailable()) {
return Object.keys(localStorage);
}
return Array.from(memoryStorage.keys());
} catch {
return Array.from(memoryStorage.keys());
}
},
/**
* Get storage size in bytes (approximate)
*
* @returns {number} Size in bytes
*
* @example
* console.log(`Storage used: ${Storage.size()} bytes`);
*/
size() {
try {
let total = 0;
const keys = this.keys();
for (const key of keys) {
const value = this.get(key);
if (value) {
total += key.length + value.length;
}
}
return total * 2; // UTF-16 uses 2 bytes per character
} catch {
return 0;
}
},
};
export default Storage;
================================================
FILE: src/assets/scripts/utils/theme.js
================================================
/**
* Adminator Theme Manager
* Handles light/dark mode switching with localStorage persistence
*
* @module utils/theme
* @example
* // Get current theme
* const current = Theme.current(); // 'light' or 'dark'
*
* // Toggle theme
* Theme.toggle();
*
* // Apply specific theme
* Theme.apply('dark');
*
* // Listen for theme changes
* window.addEventListener('adminator:themeChanged', (e) => {
* console.log('New theme:', e.detail.theme);
* });
*/
/* global Chart */
/** @constant {string} Storage key for theme preference */
const THEME_KEY = 'adminator-theme';
/** @constant {string[]} Valid theme values */
const VALID_THEMES = ['light', 'dark'];
/**
* Safe localStorage wrapper
* Handles cases where localStorage is unavailable (private browsing, etc.)
* @private
*/
const Storage = {
/**
* Get item from localStorage
* @param {string} key - Storage key
* @returns {string|null} Stored value or null
*/
get(key) {
try {
return localStorage.getItem(key);
} catch {
return null;
}
},
/**
* Set item in localStorage
* @param {string} key - Storage key
* @param {string} value - Value to store
* @returns {boolean} Success status
*/
set(key, value) {
try {
localStorage.setItem(key, value);
return true;
} catch {
return false;
}
},
};
/**
* Theme Manager
* @namespace
*/
const Theme = {
/**
* Apply a theme to the document
* Updates Chart.js defaults if available
*
* @param {('light'|'dark')} theme - Theme to apply
* @fires adminator:themeChanged
* @returns {boolean} Success status
*
* @example
* Theme.apply('dark');
*/
apply(theme) {
// Validate theme
if (!VALID_THEMES.includes(theme)) {
console.warn(`[Adminator] Invalid theme "${theme}". Using "light".`);
theme = 'light';
}
// Apply to document
document.documentElement.setAttribute('data-theme', theme);
// Update Chart.js defaults if available
if (window.Chart && Chart.defaults) {
const isDark = theme === 'dark';
const textColor = isDark ? '#FFFFFF' : '#212529';
const mutedColor = isDark ? '#D1D5DB' : '#6C757D';
const borderColor = isDark ? '#374151' : '#E2E5E8';
const gridColor = isDark ? 'rgba(209, 213, 219, 0.15)' : 'rgba(0, 0, 0, 0.05)';
const tooltipBg = isDark ? '#1F2937' : 'rgba(255, 255, 255, 0.95)';
// Set global defaults
Chart.defaults.color = textColor;
Chart.defaults.borderColor = borderColor;
Chart.defaults.backgroundColor = tooltipBg;
// Set plugin defaults
Chart.defaults.plugins.legend.labels.color = textColor;
Chart.defaults.plugins.tooltip.backgroundColor = tooltipBg;
Chart.defaults.plugins.tooltip.titleColor = textColor;
Chart.defaults.plugins.tooltip.bodyColor = textColor;
Chart.defaults.plugins.tooltip.borderColor = borderColor;
// Set scale defaults
const scales = ['category', 'linear', 'logarithmic', 'time', 'radialLinear'];
scales.forEach(scale => {
if (Chart.defaults.scales[scale]) {
Chart.defaults.scales[scale].ticks.color = mutedColor;
Chart.defaults.scales[scale].grid.color = gridColor;
}
});
// RadialLinear specific
if (Chart.defaults.scales.radialLinear) {
Chart.defaults.scales.radialLinear.pointLabels.color = mutedColor;
Chart.defaults.scales.radialLinear.angleLines.color = gridColor;
}
}
// Persist to storage
Storage.set(THEME_KEY, theme);
// Update toggle accessibility state if exists
const toggle = document.getElementById('theme-toggle');
if (toggle) {
toggle.setAttribute('aria-checked', theme === 'dark' ? 'true' : 'false');
}
// Dispatch event for other components
window.dispatchEvent(new CustomEvent('adminator:themeChanged', {
detail: { theme },
}));
return true;
},
/**
* Toggle between light and dark themes
*
* @returns {('light'|'dark')} The new theme
*
* @example
* const newTheme = Theme.toggle();
* console.log('Switched to:', newTheme);
*/
toggle() {
const next = this.current() === 'dark' ? 'light' : 'dark';
this.apply(next);
return next;
},
/**
* Get the current theme
*
* @returns {('light'|'dark')} Current theme
*
* @example
* if (Theme.current() === 'dark') {
* // Dark mode specific logic
* }
*/
current() {
const stored = Storage.get(THEME_KEY);
return VALID_THEMES.includes(stored) ? stored : 'light';
},
/**
* Initialize the theme system
* Detects OS preference on first visit, otherwise uses stored preference
*
* @returns {('light'|'dark')} The applied theme
*
* @example
* // Call once on app initialization
* Theme.init();
*/
init() {
const stored = Storage.get(THEME_KEY);
if (!stored) {
// First visit - detect OS preference
const prefersDark = window.matchMedia &&
window.matchMedia('(prefers-color-scheme: dark)').matches;
const theme = prefersDark ? 'dark' : 'light';
this.apply(theme);
return theme;
}
// Use stored preference
this.apply(this.current());
return this.current();
},
/**
* Get a CSS variable value from the document
*
* @param {string} varName - CSS variable name (with or without --)
* @returns {string} The CSS variable value
*
* @example
* const bgColor = Theme.getCSSVar('--c-bkg-body');
*/
getCSSVar(varName) {
const name = varName.startsWith('--') ? varName : `--${varName}`;
return getComputedStyle(document.documentElement).getPropertyValue(name).trim();
},
/**
* Get theme-aware colors for vector maps
*
* @returns {Object} Vector map color configuration
* @property {string} backgroundColor - Map background color
* @property {string} borderColor - Border color
* @property {string} regionColor - Default region fill color
* @property {string} markerFill - Marker fill color
* @property {string} markerStroke - Marker stroke color
* @property {string} hoverColor - Region hover color
* @property {string} selectedColor - Selected region color
* @property {string} scaleStart - Scale gradient start
* @property {string} scaleEnd - Scale gradient end
*/
getVectorMapColors() {
return {
backgroundColor: this.getCSSVar('--vmap-bg-color'),
borderColor: this.getCSSVar('--vmap-border-color'),
regionColor: this.getCSSVar('--vmap-region-color'),
markerFill: this.getCSSVar('--vmap-marker-fill'),
markerStroke: this.getCSSVar('--vmap-marker-stroke'),
hoverColor: this.getCSSVar('--vmap-hover-color'),
selectedColor: this.getCSSVar('--vmap-selected-color'),
scaleStart: this.getCSSVar('--vmap-scale-start'),
scaleEnd: this.getCSSVar('--vmap-scale-end'),
scaleLight: this.getCSSVar('--vmap-scale-light'),
scaleDark: this.getCSSVar('--vmap-scale-dark'),
};
},
/**
* Get theme-aware colors for sparkline charts
*
* @returns {Object} Sparkline color configuration
*/
getSparklineColors() {
return {
success: this.getCSSVar('--sparkline-success'),
purple: this.getCSSVar('--sparkline-purple'),
info: this.getCSSVar('--sparkline-info'),
danger: this.getCSSVar('--sparkline-danger'),
light: this.getCSSVar('--sparkline-light'),
};
},
/**
* Get theme-aware colors for Chart.js charts
*
* @returns {Object} Chart color configuration
* @property {string} textColor - Main text color
* @property {string} mutedColor - Muted/secondary text color
* @property {string} borderColor - Border color
* @property {string} gridColor - Grid line color
* @property {string} tooltipBg - Tooltip background color
*/
getChartColors() {
const isDark = this.current() === 'dark';
return {
textColor: isDark ? '#FFFFFF' : '#212529',
mutedColor: isDark ? '#D1D5DB' : '#6C757D',
borderColor: isDark ? '#374151' : '#E2E5E8',
gridColor: isDark ? 'rgba(209, 213, 219, 0.15)' : 'rgba(0, 0, 0, 0.05)',
tooltipBg: isDark ? '#1F2937' : 'rgba(255, 255, 255, 0.95)',
};
},
/**
* Check if dark mode is currently active
*
* @returns {boolean} True if dark mode is active
*
* @example
* if (Theme.isDark()) {
* // Apply dark-specific styles
* }
*/
isDark() {
return this.current() === 'dark';
},
/**
* Check if light mode is currently active
*
* @returns {boolean} True if light mode is active
*/
isLight() {
return this.current() === 'light';
},
};
export default Theme;
================================================
FILE: src/assets/scripts/vectorMaps/index.js
================================================
import jsVectorMap from 'jsvectormap';
import 'jsvectormap/dist/jsvectormap.css';
import 'jsvectormap/dist/maps/world.js';
import Theme from '../utils/theme.js';
import { Events } from '../utils';
export default (function () {
// Store map instance for cleanup
let mapInstance = null;
// Main initialization function
const vectorMapInit = () => {
const worldMapContainer = document.getElementById('world-map-marker');
if (!worldMapContainer) return;
// Remove existing map
const existingMap = document.getElementById('vmap');
if (existingMap) {
existingMap.remove();
}
// Destroy existing map instance
if (mapInstance) {
try {
mapInstance.destroy();
} catch {
// Map instance cleanup
}
mapInstance = null;
}
// Get current theme colors - using template colors directly
const isDark = Theme.current() === 'dark';
const colors = {
backgroundColor: isDark ? '#313644' : '#f9fafb',
regionColor: isDark ? '#565a5c' : '#e6eaf0',
borderColor: isDark ? '#72777a' : '#d3d9e3',
hoverColor: isDark ? '#7774e7' : '#0f9aee',
selectedColor: isDark ? '#37c936' : '#7774e7',
markerFill: isDark ? '#0f9aee' : '#7774e7',
markerStroke: isDark ? '#37c936' : '#0f9aee',
scaleStart: isDark ? '#b9c2d0' : '#e6eaf0',
scaleEnd: isDark ? '#0f9aee' : '#007bff',
textColor: isDark ? '#99abb4' : '#72777a',
};
// Create new map container
const mapContainer = document.createElement('div');
mapContainer.id = 'vmap';
mapContainer.style.height = '490px';
mapContainer.style.position = 'relative';
mapContainer.style.overflow = 'hidden';
mapContainer.style.backgroundColor = colors.backgroundColor;
mapContainer.style.borderRadius = '8px';
mapContainer.style.border = `1px solid ${colors.borderColor}`;
worldMapContainer.appendChild(mapContainer);
// Initialize JSVectorMap
try {
mapInstance = jsVectorMap({
selector: '#vmap',
map: 'world',
// Styling options
backgroundColor: 'transparent',
// Region styling
regionStyle: {
initial: {
fill: colors.regionColor,
stroke: colors.borderColor,
'stroke-width': 1,
'stroke-opacity': 0.4,
},
hover: {
fill: colors.hoverColor,
cursor: 'pointer',
},
selected: {
fill: colors.selectedColor,
},
},
// Marker styling
markerStyle: {
initial: {
r: 7,
fill: colors.markerFill,
stroke: colors.markerStroke,
'stroke-width': 2,
'stroke-opacity': 0.4,
},
hover: {
r: 10,
fill: colors.hoverColor,
'stroke-opacity': 0.8,
cursor: 'pointer',
},
},
// Markers data
markers: [
{
name: 'INDIA : 350',
coords: [21.00, 78.00],
},
{
name: 'Australia : 250',
coords: [-33.00, 151.00],
},
{
name: 'USA : 250',
coords: [36.77, -119.41],
},
{
name: 'UK : 250',
coords: [55.37, -3.41],
},
{
name: 'UAE : 250',
coords: [25.20, 55.27],
},
],
// Simplified approach - remove series for now to test base colors
// series: {
// regions: [
// {
// attribute: 'fill',
// scale: [colors.scaleStart, colors.scaleEnd],
// normalizeFunction: 'polynomial',
// values: {
// 'US': 50,
// 'SA': 30,
// 'AU': 70,
// 'IN': 40,
// 'GB': 60,
// 'LV': 80,
// },
// },
// ],
// },
// Interaction options
zoomOnScroll: false,
zoomButtons: false,
// Event handlers
onMarkerTooltipShow(event, tooltip, index) {
// Safe access to marker data
const marker = this.markers && this.markers[index];
const markerName = marker ? marker.name : `Marker ${index + 1}`;
tooltip.text(markerName);
},
onRegionTooltipShow(event, tooltip, code) {
// Safe access to region data
const regionName = (this.mapData && this.mapData.paths && this.mapData.paths[code])
? this.mapData.paths[code].name || code
: code;
const value = (this.series && this.series.regions && this.series.regions[0] && this.series.regions[0].values)
? this.series.regions[0].values[code]
: null;
tooltip.text(`${regionName}${value ? `: ${ value}` : ''}`);
},
onLoaded() {
// Map loaded successfully
},
});
// Store instance for theme updates
worldMapContainer.mapInstance = mapInstance;
} catch {
// Error initializing JSVectorMap
// Fallback: show a simple message
mapContainer.innerHTML = `
🗺️
World Map
Interactive map will load here
`;
}
};
// Theme update function
const updateMapTheme = () => {
if (mapInstance) {
const isDark = Theme.current() === 'dark';
const colors = {
backgroundColor: isDark ? '#313644' : '#f9fafb',
regionColor: isDark ? '#565a5c' : '#e6eaf0',
borderColor: isDark ? '#72777a' : '#d3d9e3',
hoverColor: isDark ? '#7774e7' : '#0f9aee',
selectedColor: isDark ? '#37c936' : '#7774e7',
markerFill: isDark ? '#0f9aee' : '#7774e7',
markerStroke: isDark ? '#37c936' : '#0f9aee',
scaleStart: isDark ? '#b9c2d0' : '#e6eaf0',
scaleEnd: isDark ? '#0f9aee' : '#007bff',
textColor: isDark ? '#99abb4' : '#72777a',
};
try {
// Update region styles - commented out series for now
// mapInstance.updateSeries('regions', {
// attribute: 'fill',
// scale: [colors.scaleStart, colors.scaleEnd],
// values: {
// 'US': 50,
// 'SA': 30,
// 'AU': 70,
// 'IN': 40,
// 'GB': 60,
// 'LV': 80,
// },
// });
// Update container background
const container = document.getElementById('vmap');
if (container) {
container.style.backgroundColor = colors.backgroundColor;
}
} catch {
// Theme update failed, reinitializing map
vectorMapInit();
}
} else {
vectorMapInit();
}
};
// Initialize map
vectorMapInit();
// Reinitialize on window resize
window.addEventListener('resize', Events.debounce(vectorMapInit, 300));
// Listen for theme changes
window.addEventListener('adminator:themeChanged', Events.debounce(updateMapTheme, 150));
// Cleanup on page unload
window.addEventListener('beforeunload', () => {
if (mapInstance) {
try {
mapInstance.destroy();
} catch {
// Map cleanup on unload
}
mapInstance = null;
}
});
// Return public API
return {
init: vectorMapInit,
updateTheme: updateMapTheme,
getInstance: () => mapInstance,
};
}());
================================================
FILE: src/assets/styles/index.scss
================================================
@use 'spec/settings/index' as *;
@use 'spec/tools/index' as *;
@use "bootstrap/scss/bootstrap" as *;
@use 'spec/index' as *;
@use 'vendor/index' as *;
@import "utils/theme.css";
@import "utils/mobile.scss";
body {
background: var(--c-bkg-body);
color: var(--c-text-base);
}
.sidebar {
background: var(--c-bkg-sidebar);
}
.bgc-white {
background: var(--c-bkg-card) !important;
}
// Dark-mode aware Header & Dropdown --------------------------------
.header {
background: var(--c-bkg-card);
border-bottom: 1px solid var(--c-border);
.dropdown-menu {
background: var(--c-bkg-card);
border: 1px solid var(--c-border);
}
.nav-left > li > a,
.nav-right > li > a {
color: var(--c-text-base);
&:hover,
&:focus {
color: var(--c-primary);
}
}
.notifications .counter {
background: var(--c-danger);
color: #fff;
}
// Theme toggle switch styling
.theme-toggle {
display: flex;
align-items: center;
height: 65px; // Match header height
padding: 0 15px;
.form-check {
margin: 0;
.form-check-label {
color: var(--c-text-muted);
font-size: 11px;
font-weight: 500;
text-transform: uppercase;
letter-spacing: 0.5px;
i {
font-size: 12px;
}
}
.form-check-input {
width: 2.5rem;
height: 1.25rem;
background-color: var(--c-border);
border: 1px solid var(--c-border);
cursor: pointer;
&:checked {
background-color: var(--c-primary);
border-color: var(--c-primary);
}
&:focus {
box-shadow: 0 0 0 0.2rem color-mix(in srgb, var(--c-primary) 25%, transparent);
border-color: var(--c-primary);
}
}
}
// Mobile theme toggle adjustments
@media (max-width: 991px) {
padding: 0 6px;
height: 65px;
.form-check {
.form-check-label {
font-size: 10px;
&:first-child {
margin-right: 4px;
}
&:last-child {
margin-left: 4px;
}
}
.form-check-input {
width: 2rem;
height: 1rem;
}
}
}
// Very small mobile adjustments
@media (max-width: 480px) {
padding: 0 4px;
.form-check {
flex-direction: column;
align-items: center;
text-align: center;
.form-check-label {
font-size: 8px;
margin: 1px 0;
white-space: nowrap;
i {
margin: 0 2px;
}
}
.form-check-input {
width: 1.5rem;
height: 0.8rem;
margin: 2px 0;
}
}
}
}
}
// Mobile dropdown menu improvements
@media (max-width: 767px) {
.header {
.nav-right {
.dropdown-menu {
position: fixed !important;
top: 65px !important;
left: 5px !important;
right: 5px !important;
width: auto !important;
max-width: none !important;
min-width: auto !important;
transform: none !important;
z-index: 1050;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
max-height: calc(100vh - 85px);
overflow-y: auto;
}
.notifications .dropdown-menu {
max-height: calc(100vh - 85px);
overflow-y: auto;
}
}
}
}
// Mobile search input overlay
@media (max-width: 480px) {
.header {
.search-input {
&.active {
position: absolute;
top: 65px;
left: 0;
right: 0;
background: var(--c-bkg-card);
border-top: 1px solid var(--c-border);
padding: 10px;
z-index: 999;
input {
margin-top: 0;
width: 100%;
padding: 10px;
border: 1px solid var(--c-border);
border-radius: 4px;
background: var(--c-bkg-card);
color: var(--c-text-base);
&::placeholder {
color: var(--c-text-muted);
}
}
}
}
}
}
// Tables -------------------------------------------------
.table {
background: var(--c-bkg-card);
color: var(--c-text-base);
thead th {
background: var(--c-bkg-card);
color: var(--c-text-base);
border-color: var(--c-border);
}
tbody {
td, th {
border-color: var(--c-border);
color: var(--c-text-base);
background: var(--c-bkg-card);
}
tr:nth-child(even) {
td, th {
background: color-mix(in srgb, var(--c-bkg-card) 95%, var(--c-border));
}
}
}
tfoot th {
background: var(--c-bkg-card);
color: var(--c-text-base);
border-color: var(--c-border);
}
// Bootstrap table variants
&.table-striped {
tbody tr:nth-child(odd) {
td, th {
background: var(--c-bkg-card);
}
}
tbody tr:nth-child(even) {
td, th {
background: color-mix(in srgb, var(--c-bkg-card) 95%, var(--c-border));
}
}
}
&.table-hover {
tbody tr:hover {
td, th {
background: color-mix(in srgb, var(--c-bkg-card) 90%, var(--c-border)) !important;
color: var(--c-text-base);
}
}
}
&.table-bordered {
border: 1px solid var(--c-border);
th, td {
border: 1px solid var(--c-border);
}
}
// Table head variants
.table-dark {
background: color-mix(in srgb, var(--c-bkg-card) 80%, #000);
th {
background: color-mix(in srgb, var(--c-bkg-card) 80%, #000);
color: var(--c-text-base);
border-color: var(--c-border);
}
}
.table-light {
background: var(--c-bkg-card);
th {
background: var(--c-bkg-card);
color: var(--c-text-base);
border-color: var(--c-border);
}
}
}
// Forms --------------------------------------------------
.form-control,
.form-select {
background: var(--c-bkg-card);
color: var(--c-text-base);
border: 1px solid var(--c-border);
&:focus {
border-color: var(--c-primary);
box-shadow: 0 0 0 0.1rem rgba(75, 124, 243, .25);
}
}
input::placeholder {
color: var(--c-text-muted);
}
// Cards --------------------------------------------------
.card,
.bgc-white.bd,
.bgc-white.bdT,
.bgc-white.bdB {
background: var(--c-bkg-card);
border-color: var(--c-border) !important;
color: var(--c-text-base);
}
// Alerts -------------------------------------------------
.alert {
color: var(--c-text-base);
border-color: var(--c-border);
background: color-mix(in srgb, var(--c-bkg-card) 85%, var(--c-border));
&.alert-primary {
background: color-mix(in srgb, var(--c-primary) 20%, var(--c-bkg-card));
border-color: var(--c-primary);
color: var(--c-primary);
}
}
// Modals -------------------------------------------------
.modal-content {
background: var(--c-bkg-card);
color: var(--c-text-base);
border: 1px solid var(--c-border);
}
// Logo colours ------------------------------------------
// Logo SVG has white "A" on primary indigo background (#6366f1)
// No CSS manipulation needed - colors are baked into the SVG
.logo-text {
color: var(--c-text-base);
}
// Ensure auth page logos are properly sized
.logo-auth {
max-width: 60px !important;
max-height: 60px !important;
width: auto;
height: auto;
}
// Generic border utility override -----------------------
.bd,
.bdT,
.bdB,
.bdL,
.bdR {
border-color: var(--c-border) !important;
}
// Sidebar logo border -----------------------------------
.sidebar-logo {
border-color: var(--c-border) !important;
}
// Grey-100 utility override -----------------------------
.bgc-grey-100 { background: color-mix(in srgb, var(--c-bkg-body) 90%, #000) !important; }
// Sales Report widget styling -------------------------------
.sales-report-header {
background-color: var(--c-primary) !important;
color: #ffffff !important;
h5, h3, p {
color: #ffffff !important;
}
}
// Hover background utilities for dark mode ----------------
[data-theme="dark"] .bgcH-grey-100:hover {
background: color-mix(in srgb, var(--c-bkg-card) 85%, var(--c-border)) !important;
}
// Sidebar right border ----------------------------------
.sidebar,
.sidebar-menu {
border-right: 1px solid var(--c-border);
}
// Dark mode text color overrides for better visibility -------
[data-theme="dark"] .c-grey-900 {
color: var(--c-text-base) !important;
}
[data-theme="dark"] .c-grey-800 {
color: var(--c-text-base) !important;
}
[data-theme="dark"] .c-grey-700 {
color: var(--c-text-muted) !important;
}
[data-theme="dark"] .c-grey-600 {
color: var(--c-text-muted) !important;
}
[data-theme="dark"] .text-dark {
color: var(--c-text-base) !important;
}
// Ensure all headings are theme-aware ----------------------
h1, h2, h3, h4, h5, h6 {
color: var(--c-text-base);
}
// Email & Compose dark mode fixes ---------------------------
.email-app {
.email-side-nav {
background: var(--c-bkg-card);
border-color: var(--c-border);
}
.email-list,
.email-content,
.email-wrapper {
background: var(--c-bkg-card) !important;
color: var(--c-text-base);
}
.email-list-item {
border-color: var(--c-border) !important;
&:hover {
background: color-mix(in srgb, var(--c-bkg-card) 85%, var(--c-border)) !important;
}
}
}
// Badge colors for dark mode ---------------------------------
[data-theme="dark"] .badge {
&.bgc-deep-purple-50 {
background: #8b5cf6 !important;
color: #fff !important;
}
&.c-deep-purple-700 {
color: #fff !important;
}
&.bgc-green-50 {
background: var(--c-success) !important;
color: #fff !important;
}
&.c-green-700 {
color: #fff !important;
}
&.bgc-blue-50 {
background: var(--c-primary) !important;
color: #fff !important;
}
&.c-blue-700 {
color: #fff !important;
}
&.bgc-amber-50 {
background: #f59e0b !important;
color: #000 !important;
}
&.c-amber-700 {
color: #000 !important;
}
&.bgc-red-50 {
background: var(--c-danger) !important;
color: #fff !important;
}
&.c-red-700 {
color: #fff !important;
}
}
// Email buttons in dark mode ---------------------------------
[data-theme="dark"] .email-app {
.btn.bgc-white {
background: var(--c-bkg-card) !important;
color: var(--c-text-base) !important;
border: 1px solid var(--c-border) !important;
&:hover {
background: color-mix(in srgb, var(--c-bkg-card) 85%, var(--c-border)) !important;
}
}
}
// Additional table styling for consistency ----------------
.table-responsive {
border: 1px solid var(--c-border);
border-radius: 3px;
}
// Table inside cards should blend seamlessly
.bgc-white .table {
background: var(--c-bkg-card);
border: none;
thead th {
border-top: none;
}
}
// Status badges in tables need proper theming
.table .badge {
&.bgc-red-50.c-red-700 {
background: var(--c-danger) !important;
color: #fff !important;
}
&.bgc-deep-purple-50.c-deep-purple-700 {
background: #8b5cf6 !important;
color: #fff !important;
}
&.bgc-pink-50.c-pink-700 {
background: #ec4899 !important;
color: #fff !important;
}
}
// Chat page specific dark mode fixes ----------------------
[data-theme="dark"] {
// Chat page loader
#loader {
background: var(--c-bkg-body) !important;
}
// Chat message bubbles - different styling for sent vs received
.ai-fs .pY-3.pX-10.bgc-white {
background: var(--c-bkg-card) !important;
border: 1px solid var(--c-border);
}
.ai-fe .pY-3.pX-10.bgc-white {
background: var(--c-primary) !important;
border: 1px solid var(--c-primary);
color: white !important;
small {
color: rgba(255, 255, 255, 0.8) !important;
}
span {
color: white !important;
}
}
// Chat status indicators (preserve their semantic colors)
.c-green-500 {
color: var(--c-success) !important;
}
.c-amber-500 {
color: #f59e0b !important;
}
.c-red-500 {
color: var(--c-danger) !important;
}
// Chat typing indicator
.lh-1 i {
color: var(--c-text-muted);
}
// Chat backgrounds
.bgc-grey-200 {
background: var(--c-bkg-body) !important;
}
}
// Todo List dark mode fixes ---------------------------------
[data-theme="dark"] {
.list-task {
background: var(--c-bkg-card);
border: 1px solid var(--c-border);
.list-group-item {
background: var(--c-bkg-card);
border-color: var(--c-border);
color: var(--c-text-base);
&:first-child {
border-top-color: var(--c-border);
}
&:last-child {
border-bottom-color: var(--c-border);
}
.form-label {
color: var(--c-text-base);
}
// Checkbox styling for dark mode
.checkbox {
input[type="checkbox"] {
&:checked + label::before {
background: var(--c-primary);
border-color: var(--c-primary);
}
&:focus + label::before {
border-color: var(--c-primary);
box-shadow: 0 0 0 0.2rem rgba(var(--c-primary-rgb), 0.25);
}
}
label::before {
background: var(--c-bkg-card);
border-color: var(--c-border);
}
}
// Todo badges
.badge {
&.bg-success {
background: var(--c-success) !important;
color: white !important;
}
&.bg-danger {
background: var(--c-danger) !important;
color: white !important;
}
&.bg-warning {
background: #f59e0b !important;
color: #000 !important;
}
&.bg-info {
background: var(--c-primary) !important;
color: white !important;
}
}
}
}
}
// Calendar page dark mode fixes -----------------------------
[data-theme="dark"] {
// Calendar event sidebar
.bgc-white.bd {
background: var(--c-bkg-card) !important;
border-color: var(--c-border) !important;
.bdB {
border-bottom-color: var(--c-border) !important;
}
// Calendar event items
.peers.ai-c.jc-sb.fxw-nw {
border-bottom-color: var(--c-border) !important;
.c-grey-900 {
color: var(--c-text-base) !important;
}
.c-grey-600 {
color: var(--c-text-muted) !important;
}
.c-grey-700 {
color: var(--c-text-muted) !important;
}
// Action buttons (edit, delete)
.c-deep-purple-500 {
&:hover.cH-blue-500 {
color: var(--c-primary) !important;
}
}
.c-red-500 {
&:hover.cH-blue-500 {
color: var(--c-danger) !important;
}
}
}
// Add event button
.btn-warning {
background: #f59e0b;
border-color: #f59e0b;
color: #000;
&:hover {
background: #d97706;
border-color: #d97706;
}
}
}
// Calendar modal
.modal-content {
background: var(--c-bkg-card);
border: 1px solid var(--c-border);
color: var(--c-text-base);
.modal-body {
.form-label {
color: var(--c-text-base);
}
.form-control {
background: var(--c-bkg-body);
border-color: var(--c-border);
color: var(--c-text-base);
&::placeholder {
color: var(--c-text-muted);
}
&:focus {
background: var(--c-bkg-body);
border-color: var(--c-primary);
box-shadow: 0 0 0 0.2rem rgba(var(--c-primary-rgb), 0.25);
}
}
.input-group-text {
background: var(--c-bkg-card) !important;
border-color: var(--c-border);
color: var(--c-text-base);
&.bgc-white {
background: var(--c-bkg-card) !important;
}
}
.btn-primary {
background: var(--c-primary);
border-color: var(--c-primary);
&:hover {
background: var(--c-primary-hover);
border-color: var(--c-primary-hover);
}
}
}
}
// Calendar grid improvements
.fc {
// Calendar day cells
.fc-day {
background: var(--c-bkg-card);
&:hover {
background: color-mix(in srgb, var(--c-bkg-card) 90%, var(--c-border));
}
}
// Calendar header
.fc-head {
background: var(--c-bkg-card);
}
// Weekend styling
.fc-sun,
.fc-sat {
background: color-mix(in srgb, var(--c-bkg-card) 95%, var(--c-border));
}
// Other days
.fc-other-month {
.fc-day-number {
color: var(--c-text-muted) !important;
}
}
// Event hover effects
.fc-event {
&:hover {
opacity: 0.9;
}
}
}
}
================================================
FILE: src/assets/styles/spec/components/easyPieChart.scss
================================================
.easy-pie-chart {
position: relative;
span {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
line-height: 0;
}
}
================================================
FILE: src/assets/styles/spec/components/footer.scss
================================================
footer {
z-index: 1;
position: relative;
}
================================================
FILE: src/assets/styles/spec/components/forms.scss
================================================
@use '../settings/baseColors' as *;
@use '../settings/materialColors' as *;
.checkbox label {
display: inline-block;
vertical-align: middle;
position: relative;
padding-left: 17px;
margin-bottom: 0;
}
.checkbox label::before {
content: "";
display: inline-block;
position: absolute;
width: 17px;
height: 17px;
left: 0;
top: 50%;
transform: translateY(-50%);
margin-left: -12px;
border: 1px solid $grey-300;
border-radius: 3px;
background-color: $md-white;
-webkit-transition: border 0.15s ease-in-out, color 0.15s ease-in-out;
-o-transition: border 0.15s ease-in-out, color 0.15s ease-in-out;
transition: border 0.15s ease-in-out, color 0.15s ease-in-out;
}
.checkbox label::after {
display: inline-block;
position: absolute;
width: 17px;
height: 17px;
text-align: center;
font-size: 10px !important;
line-height: 17px;
left: 0;
top: 50%;
transform: translateY(-50%);
margin-left: -12px;
color: $grey-500;
}
.checkbox input[type="checkbox"] {
opacity: 0;
z-index: 1;
}
.checkbox input[type="checkbox"]:focus + label::before {
outline: thin dotted;
outline: 5px auto -webkit-focus-ring-color;
outline-offset: -2px;
}
.checkbox input[type="checkbox"]:checked + label::after {
font-family: 'FontAwesome';
content: "\f00c";
font-size: 13px;
}
.checkbox input[type="checkbox"]:disabled + label {
opacity: 0.65;
}
.checkbox input[type="checkbox"]:disabled + label::before {
background-color: $grey-400;
cursor: not-allowed;
}
.checkbox.checkbox-circle label::before {
border-radius: 50%;
}
.checkbox.checkbox-inline {
margin-top: 0;
}
.checkbox-primary input[type="checkbox"]:checked + label::before {
background-color: $default-primary;
border-color: $default-primary;
}
.checkbox-primary input[type="checkbox"]:checked + label::after {
color: #fff;
}
.checkbox-danger input[type="checkbox"]:checked + label::before {
background-color: $default-danger;
border-color: $default-danger;
}
.checkbox-danger input[type="checkbox"]:checked + label::after {
color: $md-white;
}
.checkbox-info input[type="checkbox"]:checked + label::before {
background-color: $default-info;
border-color: $default-info;
}
.checkbox-info input[type="checkbox"]:checked + label::after {
color: $md-white;
}
.checkbox-warning input[type="checkbox"]:checked + label::before {
background-color: $default-warning;
border-color: $default-warning;
}
.checkbox-warning input[type="checkbox"]:checked + label::after {
color: $md-white;
}
.checkbox-success input[type="checkbox"]:checked + label::before {
background-color: $default-success;
border-color: $default-success;
}
.checkbox-success input[type="checkbox"]:checked + label::after {
color: $md-white;
}
.radio {
padding-left: 20px;
}
.radio label {
display: inline-block;
vertical-align: middle;
position: relative;
padding-left: 5px;
}
.radio label::before {
content: "";
display: inline-block;
position: absolute;
width: 17px;
height: 17px;
left: 0;
margin-left: -20px;
border: 1px solid #cccccc;
border-radius: 50%;
background-color: var(--c-bkg-card);
-webkit-transition: border 0.15s ease-in-out;
-o-transition: border 0.15s ease-in-out;
transition: border 0.15s ease-in-out;
}
.radio label::after {
display: inline-block;
position: absolute;
content: " ";
width: 11px;
height: 11px;
left: 3px;
top: 3px;
margin-left: -20px;
border-radius: 50%;
background-color: var(--c-text-base);
-webkit-transform: scale(0, 0);
-ms-transform: scale(0, 0);
-o-transform: scale(0, 0);
transform: scale(0, 0);
-webkit-transition: -webkit-transform 0.1s cubic-bezier(0.8, -0.33, 0.2, 1.33);
-moz-transition: -moz-transform 0.1s cubic-bezier(0.8, -0.33, 0.2, 1.33);
-o-transition: -o-transform 0.1s cubic-bezier(0.8, -0.33, 0.2, 1.33);
transition: transform 0.1s cubic-bezier(0.8, -0.33, 0.2, 1.33);
}
.radio input[type="radio"] {
opacity: 0;
z-index: 1;
}
.radio input[type="radio"]:focus + label::before {
outline: thin dotted;
outline: 5px auto -webkit-focus-ring-color;
outline-offset: -2px;
}
.radio input[type="radio"]:checked + label::after {
-webkit-transform: scale(1, 1);
-ms-transform: scale(1, 1);
-o-transform: scale(1, 1);
transform: scale(1, 1);
}
.radio input[type="radio"]:disabled + label {
opacity: 0.65;
}
.radio input[type="radio"]:disabled + label::before {
cursor: not-allowed;
}
.radio.radio-inline {
margin-top: 0;
}
.radio-primary input[type="radio"] + label::after {
background-color: #428bca;
}
.radio-primary input[type="radio"]:checked + label::before {
border-color: #428bca;
}
.radio-primary input[type="radio"]:checked + label::after {
background-color: #428bca;
}
.radio-danger input[type="radio"] + label::after {
background-color: #d9534f;
}
.radio-danger input[type="radio"]:checked + label::before {
border-color: #d9534f;
}
.radio-danger input[type="radio"]:checked + label::after {
background-color: #d9534f;
}
.radio-info input[type="radio"] + label::after {
background-color: #5bc0de;
}
.radio-info input[type="radio"]:checked + label::before {
border-color: #5bc0de;
}
.radio-info input[type="radio"]:checked + label::after {
background-color: #5bc0de;
}
.radio-warning input[type="radio"] + label::after {
background-color: #f0ad4e;
}
.radio-warning input[type="radio"]:checked + label::before {
border-color: #f0ad4e;
}
.radio-warning input[type="radio"]:checked + label::after {
background-color: #f0ad4e;
}
.radio-success input[type="radio"] + label::after {
background-color: #5cb85c;
}
.radio-success input[type="radio"]:checked + label::before {
border-color: #5cb85c;
}
.radio-success input[type="radio"]:checked + label::after {
background-color: #5cb85c;
}
// Dark mode specific fixes for birthdate field only
[data-theme="dark"] {
// Date picker specific styling (birthdate field)
.timepicker-input {
.input-group-text {
background-color: var(--c-bkg-card) !important;
border-color: var(--c-border) !important;
color: var(--c-text-base) !important;
// Override specific background classes
&.bgc-white {
background-color: var(--c-bkg-card) !important;
}
// Calendar icon styling
.ti-calendar {
color: var(--c-text-base) !important;
}
}
.form-control {
&.bdc-grey-200 {
border-color: var(--c-border) !important;
}
}
}
}
================================================
FILE: src/assets/styles/spec/components/index.scss
================================================
@use 'sidebar' as *;
@use 'topbar' as *;
@use 'pageContainer' as *;
@use 'progressBar' as *;
@use 'easyPieChart' as *;
@use 'forms' as *;
@use 'masonry' as *;
@use 'loader' as *;
@use 'footer' as *;
@use 'modernize' as *;
================================================
FILE: src/assets/styles/spec/components/loader.scss
================================================
#loader {
transition: all 0.3s ease-in-out;
opacity: 1;
visibility: visible;
position: fixed;
height: 100vh;
width: 100%;
background: var(--loader-bg);
z-index: 90000;
}
#loader.fadeOut {
opacity: 0;
visibility: hidden;
}
.spinner {
width: 40px;
height: 40px;
position: absolute;
top: calc(50% - 20px);
left: calc(50% - 20px);
background-color: var(--spinner-bg);
border-radius: 100%;
-webkit-animation: sk-scaleout 1.0s infinite ease-in-out;
animation: sk-scaleout 1.0s infinite ease-in-out;
}
@-webkit-keyframes sk-scaleout {
0% { -webkit-transform: scale(0) }
100% {
-webkit-transform: scale(1.0);
opacity: 0;
}
}
@keyframes sk-scaleout {
0% {
-webkit-transform: scale(0);
transform: scale(0);
} 100% {
-webkit-transform: scale(1.0);
transform: scale(1.0);
opacity: 0;
}
}
================================================
FILE: src/assets/styles/spec/components/masonry.scss
================================================
================================================
FILE: src/assets/styles/spec/components/modernize.scss
================================================
// ---------------------------------------------------------
// @Modern Design Enhancements
// Subtle improvements for a contemporary look
// ---------------------------------------------------------
// ---------------------------------------------------------
// @Body & Background
// ---------------------------------------------------------
body {
background-color: var(--c-bkg-body);
// Subtle dot pattern for depth
background-image: radial-gradient(circle at 1px 1px, var(--c-border) 1px, transparent 0);
background-size: 24px 24px;
}
// ---------------------------------------------------------
// @Cards - Modern styling
// ---------------------------------------------------------
.card {
background: var(--c-bkg-card);
border: 1px solid var(--c-border);
border-radius: var(--radius-md);
box-shadow: var(--shadow-card);
transition: box-shadow 0.2s ease, transform 0.2s ease;
&:hover {
box-shadow: var(--shadow-md);
}
}
.card-header {
background: transparent;
border-bottom: 1px solid var(--c-border-light);
padding: 1rem 1.25rem;
font-weight: 600;
color: var(--c-text-base);
}
.card-body {
padding: 1.25rem;
}
.card-footer {
background: var(--c-bkg-hover);
border-top: 1px solid var(--c-border-light);
padding: 0.875rem 1.25rem;
}
// ---------------------------------------------------------
// @Stat Cards - Dashboard widgets
// ---------------------------------------------------------
.peers {
&.ai-c {
.peer {
&:first-child {
// Icon container styling
.layers {
border-radius: var(--radius-md);
transition: transform 0.2s ease;
&:hover {
transform: scale(1.05);
}
}
}
}
}
}
// Stat value styling
.fw-500 {
font-weight: 600;
letter-spacing: -0.02em;
}
// ---------------------------------------------------------
// @Tables - Modern look
// ---------------------------------------------------------
.table {
--bs-table-bg: var(--c-bkg-card);
thead {
th {
background: var(--c-bkg-hover);
border-bottom: 2px solid var(--c-border);
color: var(--c-text-base);
font-weight: 600;
font-size: 0.75rem;
text-transform: uppercase;
letter-spacing: 0.05em;
padding: 0.875rem 1rem;
}
}
tbody {
tr {
transition: background-color 0.15s ease;
&:hover {
background-color: var(--c-bkg-hover);
}
td {
padding: 0.875rem 1rem;
border-bottom: 1px solid var(--c-border-light);
vertical-align: middle;
}
}
}
}
.table-bordered {
border: 1px solid var(--c-border);
border-radius: var(--radius-md);
overflow: hidden;
th,
td {
border-color: var(--c-border-light);
}
}
// ---------------------------------------------------------
// @Buttons - Refined styling
// ---------------------------------------------------------
.btn {
border-radius: var(--radius-sm);
font-weight: 500;
padding: 0.5rem 1rem;
transition: all 0.2s ease;
border: none;
&:focus {
box-shadow: 0 0 0 3px rgb(99 102 241 / 0.25);
}
}
.btn-primary {
background: linear-gradient(135deg, var(--c-primary) 0%, var(--c-primary-dark) 100%);
&:hover {
background: linear-gradient(135deg, var(--c-primary-light) 0%, var(--c-primary) 100%);
transform: translateY(-1px);
box-shadow: var(--shadow-md);
}
&:active {
transform: translateY(0);
}
}
.btn-success {
background: linear-gradient(135deg, var(--c-success) 0%, #059669 100%);
&:hover {
transform: translateY(-1px);
box-shadow: var(--shadow-md);
}
}
.btn-danger {
background: linear-gradient(135deg, var(--c-danger) 0%, #dc2626 100%);
&:hover {
transform: translateY(-1px);
box-shadow: var(--shadow-md);
}
}
.btn-outline-primary {
border: 1.5px solid var(--c-primary);
color: var(--c-primary);
background: transparent;
&:hover {
background: var(--c-primary);
color: white;
transform: translateY(-1px);
}
}
// ---------------------------------------------------------
// @Form Controls - Modern inputs
// ---------------------------------------------------------
.form-control,
.form-select {
background-color: var(--c-bkg-card);
border: 1.5px solid var(--c-border);
border-radius: var(--radius-sm);
color: var(--c-text-base);
padding: 0.625rem 0.875rem;
transition: border-color 0.2s ease, box-shadow 0.2s ease;
&:focus {
border-color: var(--c-primary);
box-shadow: 0 0 0 3px rgb(99 102 241 / 0.15);
outline: none;
}
&::placeholder {
color: var(--c-text-light);
}
}
.form-label {
color: var(--c-text-base);
font-weight: 500;
font-size: 0.875rem;
margin-bottom: 0.5rem;
}
// ---------------------------------------------------------
// @Badges - Modern pills
// ---------------------------------------------------------
.badge {
border-radius: 50px;
font-weight: 500;
font-size: 0.75rem;
padding: 0.35em 0.75em;
letter-spacing: 0.02em;
}
.bg-primary {
background: linear-gradient(135deg, var(--c-primary) 0%, var(--c-primary-dark) 100%) !important;
}
.bg-success {
background: linear-gradient(135deg, var(--c-success) 0%, #059669 100%) !important;
}
.bg-danger {
background: linear-gradient(135deg, var(--c-danger) 0%, #dc2626 100%) !important;
}
.bg-warning {
background: linear-gradient(135deg, var(--c-warning) 0%, #d97706 100%) !important;
color: #1e293b !important;
}
.bg-info {
background: linear-gradient(135deg, var(--c-info) 0%, #0284c7 100%) !important;
}
// ---------------------------------------------------------
// @Dropdowns - Refined menus
// ---------------------------------------------------------
.dropdown-menu {
background: var(--c-bkg-card);
border: 1px solid var(--c-border);
border-radius: var(--radius-md);
box-shadow: var(--shadow-lg);
padding: 0.5rem;
margin-top: 0.5rem;
.dropdown-item {
border-radius: var(--radius-sm);
color: var(--c-text-base);
padding: 0.5rem 0.875rem;
transition: background-color 0.15s ease;
&:hover,
&:focus {
background-color: var(--c-bkg-hover);
color: var(--c-primary);
}
&.active,
&:active {
background-color: var(--c-primary);
color: white;
}
}
.dropdown-divider {
border-top-color: var(--c-border-light);
margin: 0.5rem 0;
}
}
// ---------------------------------------------------------
// @Progress Bars - Modern look
// ---------------------------------------------------------
.progress {
background-color: var(--c-border-light);
border-radius: 50px;
height: 8px;
overflow: hidden;
}
.progress-bar {
border-radius: 50px;
background: linear-gradient(90deg, var(--c-primary) 0%, var(--c-primary-light) 100%);
}
// ---------------------------------------------------------
// @Alerts - Soft backgrounds
// ---------------------------------------------------------
.alert {
border: none;
border-radius: var(--radius-md);
border-left: 4px solid;
}
.alert-primary {
background-color: rgb(99 102 241 / 0.1);
border-left-color: var(--c-primary);
color: var(--c-primary-dark);
}
.alert-success {
background-color: rgb(16 185 129 / 0.1);
border-left-color: var(--c-success);
color: #047857;
}
.alert-danger {
background-color: rgb(239 68 68 / 0.1);
border-left-color: var(--c-danger);
color: #b91c1c;
}
.alert-warning {
background-color: rgb(245 158 11 / 0.1);
border-left-color: var(--c-warning);
color: #92400e;
}
.alert-info {
background-color: rgb(14 165 233 / 0.1);
border-left-color: var(--c-info);
color: #0369a1;
}
// ---------------------------------------------------------
// @Nav Tabs - Modern tabs
// ---------------------------------------------------------
.nav-tabs {
border-bottom: 2px solid var(--c-border-light);
gap: 0.25rem;
.nav-link {
border: none;
border-radius: var(--radius-sm) var(--radius-sm) 0 0;
color: var(--c-text-muted);
font-weight: 500;
padding: 0.75rem 1.25rem;
transition: all 0.2s ease;
position: relative;
&:hover {
color: var(--c-primary);
background-color: var(--c-bkg-hover);
}
&.active {
color: var(--c-primary);
background-color: var(--c-bkg-card);
&::after {
content: '';
position: absolute;
bottom: -2px;
left: 0;
right: 0;
height: 2px;
background: var(--c-primary);
}
}
}
}
// ---------------------------------------------------------
// @Pagination - Modern style
// ---------------------------------------------------------
.pagination {
gap: 0.25rem;
.page-link {
border: none;
border-radius: var(--radius-sm);
color: var(--c-text-base);
font-weight: 500;
min-width: 36px;
height: 36px;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s ease;
&:hover {
background-color: var(--c-bkg-hover);
color: var(--c-primary);
}
}
.page-item {
&.active .page-link {
background: linear-gradient(135deg, var(--c-primary) 0%, var(--c-primary-dark) 100%);
color: white;
}
&.disabled .page-link {
color: var(--c-text-light);
background: transparent;
}
}
}
// ---------------------------------------------------------
// @List Groups - Refined lists
// ---------------------------------------------------------
.list-group {
border-radius: var(--radius-md);
overflow: hidden;
}
.list-group-item {
background-color: var(--c-bkg-card);
border: none;
border-bottom: 1px solid var(--c-border-light);
padding: 1rem 1.25rem;
transition: background-color 0.15s ease;
&:last-child {
border-bottom: none;
}
&:hover {
background-color: var(--c-bkg-hover);
}
&.active {
background: linear-gradient(135deg, var(--c-primary) 0%, var(--c-primary-dark) 100%);
border-color: transparent;
color: white;
}
}
// ---------------------------------------------------------
// @Tooltips & Popovers
// ---------------------------------------------------------
.tooltip-inner {
background-color: var(--c-text-base);
border-radius: var(--radius-sm);
font-size: 0.8125rem;
padding: 0.5rem 0.75rem;
}
.popover {
border: 1px solid var(--c-border);
border-radius: var(--radius-md);
box-shadow: var(--shadow-lg);
}
// ---------------------------------------------------------
// @Modals - Modern dialogs
// ---------------------------------------------------------
.modal-content {
background: var(--c-bkg-card);
border: 1px solid var(--c-border);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-lg);
}
.modal-header {
border-bottom: 1px solid var(--c-border-light);
padding: 1.25rem 1.5rem;
}
.modal-body {
padding: 1.5rem;
}
.modal-footer {
border-top: 1px solid var(--c-border-light);
padding: 1rem 1.5rem;
}
// ---------------------------------------------------------
// @Scrollbar - Custom styling
// ---------------------------------------------------------
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: var(--c-bkg-body);
}
::-webkit-scrollbar-thumb {
background: var(--c-border);
border-radius: 4px;
&:hover {
background: var(--c-text-light);
}
}
// ---------------------------------------------------------
// @Dark Mode Refinements
// ---------------------------------------------------------
[data-theme="dark"] {
body {
background-image: radial-gradient(circle at 1px 1px, var(--c-border) 0.5px, transparent 0);
}
.card {
border-color: var(--c-border);
box-shadow: var(--shadow-card);
}
.table {
--bs-table-bg: var(--c-bkg-card);
--bs-table-hover-bg: var(--c-bkg-hover);
}
.form-control,
.form-select {
background-color: var(--c-bkg-body);
border-color: var(--c-border);
&:focus {
border-color: var(--c-primary);
box-shadow: 0 0 0 3px rgb(129 140 248 / 0.2);
}
}
.alert-primary {
background-color: rgb(129 140 248 / 0.15);
color: var(--c-primary-light);
}
.alert-success {
background-color: rgb(52 211 153 / 0.15);
color: var(--c-success);
}
.alert-danger {
background-color: rgb(248 113 113 / 0.15);
color: var(--c-danger);
}
.alert-warning {
background-color: rgb(251 191 36 / 0.15);
color: var(--c-warning);
}
.alert-info {
background-color: rgb(56 189 248 / 0.15);
color: var(--c-info);
}
}
================================================
FILE: src/assets/styles/spec/components/pageContainer.scss
================================================
@use '../settings/baseColors' as *;
@use '../settings/breakpoints' as *;
@use '../tools/mixins/mediaQueriesRanges' as *;
// ---------------------------------------------------------
// @TOC
// ---------------------------------------------------------
// + @Page Container
// + @Main Content
// + @Full Container
// + @Collapsed State
// + @Mobile Layout Fixes
// ---------------------------------------------------------
// @Page Container - MODERN LAYOUT APPROACH
// ---------------------------------------------------------
.page-container {
min-height: 100vh;
padding-left: $offscreen-size;
transition: all 0.2s ease;
// Modern flexbox layout to prevent footer overlap
display: flex;
flex-direction: column;
@include between($breakpoint-md, $breakpoint-xl) {
padding-left: $collapsed-size;
}
@include to($breakpoint-md) {
padding-left: 0;
}
}
// ---------------------------------------------------------
// @Main Content - FLEXIBLE LAYOUT
// ---------------------------------------------------------
.main-content {
padding: 85px 20px 20px;
// Flex-grow to push footer to bottom
flex: 1 0 auto;
min-height: 0; // Allow flex shrinking
// Ensure content doesn't overflow
overflow-x: hidden;
@include to($breakpoint-md) {
padding: 85px 5px 5px;
}
@include to($breakpoint-sm) {
padding: 85px 10px 30px; // Extra bottom padding on mobile
}
@include to(400px) {
padding: 85px 5px 40px; // Even more bottom padding on tiny screens
}
}
.remain-height {
height: calc(100vh - 126px);
}
// ---------------------------------------------------------
// @Full Container
// ---------------------------------------------------------
.full-container {
left: $offscreen-size;
min-height: calc(100vh - #{$header-height});
position: absolute;
right: 0;
top: $header-height;
transition: all 0.2s ease;
@include between($breakpoint-md, $breakpoint-xl) {
left: 0;
padding-left: $collapsed-size;
}
@include to($breakpoint-md) {
left: 0;
}
}
// ---------------------------------------------------------
// @Mobile Layout Fixes - AGGRESSIVE FOOTER SOLUTION
// ---------------------------------------------------------
// Footer - completely redesigned for mobile
footer {
// Flex-shrink: 0 to prevent compression
flex: 0 0 auto;
// Positioning
position: relative;
z-index: 1;
// Styling
margin-top: auto;
padding: 20px;
text-align: center;
border-top: 1px solid var(--c-border);
background: var(--c-bkg-card);
color: var(--c-text-muted);
font-size: 12px;
line-height: 1.4;
// Prevent any potential overflow
word-wrap: break-word;
overflow-wrap: break-word;
// Tablet footer adjustments
@include to($breakpoint-md) {
padding: 18px 15px;
font-size: 11px;
}
// Mobile footer adjustments
@include to($breakpoint-sm) {
padding: 15px 10px;
font-size: 10px;
line-height: 1.3;
span {
display: inline-block;
max-width: 100%;
a {
color: var(--c-primary);
text-decoration: none;
word-break: break-word;
&:hover {
text-decoration: underline;
}
}
}
}
// Tiny screen footer adjustments
@include to(400px) {
padding: 12px 8px;
font-size: 9px;
line-height: 1.2;
span {
display: block;
// Break long text on new lines for readability
&::after {
content: "";
display: block;
height: 2px;
}
}
}
}
// Ensure body and html take full height for flex layout
html, body {
height: 100%;
margin: 0;
padding: 0;
}
// Global mobile overflow prevention
@include to($breakpoint-sm) {
body {
overflow-x: hidden;
}
// Prevent any element from causing horizontal scroll
* {
max-width: 100%;
box-sizing: border-box;
}
}
// Additional mobile content spacing
@include to($breakpoint-sm) {
.page-container {
.main-content {
// Extra margin-bottom to ensure footer never overlaps
margin-bottom: 20px;
// Responsive content adjustments
.row {
margin-left: 0;
margin-right: 0;
}
.col-md-6, .col-md-3, .col-md-12 {
padding-left: 5px;
padding-right: 5px;
}
}
}
}
// Emergency footer overlap prevention
@include to(480px) {
.page-container {
// Force minimum height that accounts for content
min-height: calc(100vh - 80px);
.main-content {
// Ensure there's always space for footer
padding-bottom: 60px !important;
margin-bottom: 20px !important;
}
}
footer {
// Stick to bottom on very small screens
position: relative;
margin-top: auto;
clear: both;
}
}
// ---------------------------------------------------------
// @Collapsed State
// ---------------------------------------------------------
.is-collapsed {
.page-container {
padding-left: $collapsed-size;
@include to($breakpoint-md) {
padding-left: 0;
}
@include between($breakpoint-md, $breakpoint-xl) {
padding-left: $offscreen-size;
}
}
.full-container {
left: $collapsed-size;
@include to($breakpoint-md) {
left: 0;
}
@include between($breakpoint-md, $breakpoint-xl) {
left: $offscreen-size;
padding-left: 0;
}
}
}
================================================
FILE: src/assets/styles/spec/components/progressBar.scss
================================================
.progress {
height: 4px;
background-color: var(--c-border);
border-radius: 4px;
margin-bottom: 10px;
}
================================================
FILE: src/assets/styles/spec/components/sidebar.scss
================================================
@use '../settings/baseColors' as *;
@use '../tools/mixins/mediaQueriesRanges' as *;
@use '../settings/breakpoints' as *;
@use '../tools/mixins/clearfix' as *;
@use '../settings/materialColors' as *;
// ---------------------------------------------------------
// @TOC
// ---------------------------------------------------------
// + @Sidebar
// + @Sidebar Inner
// + @Sidebar Header
// + @Sidebar Menu
// + @Sidebar Collapsed
// ---------------------------------------------------------
// @Sidebar
// ---------------------------------------------------------
.sidebar {
background-color: var(--c-bkg-sidebar);
border-right: 1px solid var(--c-border);
bottom: 0;
overflow: hidden;
position: fixed;
top: 0;
transition: all 0.2s ease;
width: $offscreen-size;
z-index: 1000;
ul {
list-style-type: none;
}
@include between($breakpoint-md, $breakpoint-xl) {
width: $collapsed-size;
.sidebar-inner {
.sidebar-logo {
border-bottom: 1px solid transparent;
padding: 0;
a {
.logo {
background-position: center center;
width: $collapsed-size;
}
}
}
.sidebar-menu {
overflow-x: hidden;
> li {
> a {
.title {
display: none;
}
}
}
li {
&.dropdown {
.arrow {
opacity: 0;
}
&.open {
ul.dropdown-menu {
display: none !important;
}
}
}
}
}
}
&:hover {
width: $offscreen-size;
.sidebar-inner {
.sidebar-logo {
border-bottom: 1px solid $border-color;
padding: 0 20px;
}
.sidebar-menu {
> li {
> a {
.title {
display: inline-block;
}
}
}
li {
&.dropdown {
.arrow {
opacity: 1;
}
}
&.open {
> ul.dropdown-menu {
display: block !important;
}
}
}
}
}
}
}
@include to($breakpoint-md) {
left: -$offscreen-size;
width: calc(#{$offscreen-size} - 30px);
}
}
// ---------------------------------------------------------
// @Sidebar Inner
// ---------------------------------------------------------
.sidebar-inner {
position: relative;
height: 100%;
}
// ---------------------------------------------------------
// @Sidebar Header
// ---------------------------------------------------------
.sidebar-logo {
border-bottom: 1px solid var(--c-border);
line-height: 0;
padding: 0 20px;
transition: all 0.2s ease;
background: var(--c-bkg-card);
a {
display: inline-block;
width: 100%;
.logo {
background-position: center left;
background-repeat: no-repeat;
display: inline-block;
min-height: calc(#{$header-height} - 1px);
width: 100%;
width: 70px;
img {
height: 42px;
width: auto;
display: block;
margin: 11px auto;
}
}
.logo-text {
color: var(--c-text-base);
}
}
.mobile-toggle {
display: none;
float: right;
font-size: 18px;
line-height: calc(#{$header-height} - 1px);
a {
color: var(--c-icon);
&:hover {
color: var(--c-icon-hover);
}
}
@include to($breakpoint-md) {
display: inline-block;
}
@include between($breakpoint-md, $breakpoint-xl) {
display: none;
}
}
}
// ---------------------------------------------------------
// @Sidebar Menu
// ---------------------------------------------------------
.sidebar-menu {
@include clearfix;
height: calc(100vh - #{$header-height});
list-style: none;
margin: 0;
overflow: auto;
padding: 0.75rem 0;
position: relative;
.dropdown-toggle::after {
display: none;
}
.sidebar-link {
&.actived::before {
background: $md-blue-500;
border-radius: 50%;
content: '';
display: block;
height: 8px;
left: -4px;
position: absolute;
top: calc(50% - 4px);
width: 8px;
}
}
li {
position: relative;
&.dropdown {
.arrow {
font-size: 10px;
line-height: 40px;
position: absolute;
right: 30px;
transition: all 0.05s ease-in;
@include to($breakpoint-md) {
right: 25px;
}
}
&.open {
> a {
color: var(--c-text-base);
.icon-holder {
color: var(--c-icon-hover);
}
> .arrow {
transform: rotate(90deg);
}
}
> .dropdown-menu {
display: block;
.dropdown-menu {
padding-left: 20px;
}
.arrow {
line-height: 25px;
}
}
}
}
a {
color: var(--c-text-muted);
transition: all 0.3s ease;
&:hover,
&:focus {
color: var(--c-text-base);
text-decoration: none;
.icon-holder {
color: var(--c-icon-hover);
}
}
}
}
> li {
&.dropdown {
ul {
&.dropdown-menu {
background-color: transparent;
border-radius: 0;
border: 0;
box-shadow: none;
float: none;
padding-left: 50px;
padding-top: 0;
position: relative;
width: 100%;
> li {
> a {
display: block;
padding: 10px 15px;
&:hover,
&:focus {
background-color: transparent;
color: var(--c-text-base);
}
}
&.actived {
a {
color: var(--c-text-base);
}
}
}
}
}
}
> a {
display: block;
font-size: 15px;
font-weight: 500;
padding: 5px 15px;
position: relative;
white-space: nowrap;
.icon-holder {
border-radius: 6px;
color: var(--c-icon);
display: inline-block;
font-size: 17px;
height: 35px;
left: 0;
line-height: 35px;
margin-right: 14px;
position: relative;
text-align: center;
transition: all 0.3s ease;
width: 35px;
}
}
}
}
// ---------------------------------------------------------
// @Sidebar Collapsed
// ---------------------------------------------------------
.is-collapsed {
.sidebar {
@include from($breakpoint-xl) {
width: $collapsed-size;
.sidebar-inner {
.sidebar-logo {
border-bottom: 1px solid transparent;
padding: 0;
}
.sidebar-menu {
overflow-x: hidden;
> li {
> a {
.title {
display: none;
}
}
}
li {
&.dropdown {
.arrow {
opacity: 0;
}
&.open {
ul.dropdown-menu {
display: none !important;
}
}
}
}
}
}
&:hover {
width: $offscreen-size;
.sidebar-inner {
.sidebar-logo {
border-bottom: 1px solid $border-color;
padding: 0 20px;
}
.sidebar-menu {
> li {
> a {
.title {
display: inline-block;
}
}
}
li {
&.dropdown {
.arrow {
opacity: 1;
}
}
&.open {
> ul.dropdown-menu {
display: block !important;
}
}
}
}
}
}
}
@include between($breakpoint-md, $breakpoint-xl) {
width: $offscreen-size;
.sidebar-inner {
.sidebar-logo {
border-bottom: 1px solid $border-color;
padding: 0 20px;
> a {
.logo {
background-position: center left;
width: 150px;
}
}
}
.sidebar-menu {
> li {
> a {
.title {
display: inline-block;
}
}
}
li {
&.dropdown {
.arrow {
opacity: 1;
}
}
&.open {
> ul.dropdown-menu {
display: block !important;
}
}
}
}
}
}
@include to($breakpoint-md) {
left: 0;
}
}
}
// Dark mode sidebar improvements - subtle, cohesive dark theme
[data-theme="dark"] {
.sidebar {
background-color: var(--c-bkg-card);
.sidebar-menu {
border-right-color: var(--c-border);
li {
a {
color: var(--c-text-muted);
.icon-holder {
color: var(--c-icon);
}
&:hover,
&:focus {
color: var(--c-text-base);
background-color: var(--c-bkg-hover);
.icon-holder {
color: var(--c-icon-hover);
}
}
}
&.dropdown {
&.open {
> a {
color: var(--c-text-base);
background-color: var(--c-bkg-hover);
.icon-holder {
color: var(--c-icon-hover);
}
}
}
}
// Active menu item styling for dark mode - subtle left border accent
&.actived {
> a {
color: var(--c-text-base) !important;
background-color: var(--c-bkg-hover) !important;
border-left: 3px solid var(--c-primary) !important;
margin-left: -3px;
.icon-holder {
color: var(--c-primary) !important;
}
}
}
// Styling for dropdown parent when it has an active child
&.dropdown.has-active-child {
> a {
color: var(--c-text-base) !important;
background-color: transparent !important;
border-left: 3px solid var(--c-primary) !important;
margin-left: -3px;
.icon-holder {
color: var(--c-primary) !important;
}
}
}
}
// Dropdown menu items
> li {
&.dropdown {
ul.dropdown-menu {
> li {
> a {
color: var(--c-text-muted);
&:hover,
&:focus {
color: var(--c-text-base);
background-color: var(--c-bkg-hover);
}
}
&.actived {
a {
color: var(--c-primary) !important;
background-color: transparent !important;
font-weight: 500;
}
}
}
}
}
}
}
}
.sidebar-logo {
border-bottom-color: var(--c-border);
border-right-color: var(--c-border);
a {
.logo-text {
color: var(--c-text-base);
}
}
.mobile-toggle {
a {
color: var(--c-icon);
&:hover {
color: var(--c-icon-hover);
}
}
}
}
}
// Light mode active states (ensure proper visibility)
[data-theme="light"], :root {
.sidebar {
.sidebar-menu {
li {
&.actived {
> a {
background-color: color-mix(in srgb, #ffffff 85%, var(--c-primary)) !important;
color: var(--c-primary) !important;
.icon-holder {
color: var(--c-primary) !important;
background-color: color-mix(in srgb, var(--c-primary) 10%, transparent) !important;
}
}
}
// Styling for dropdown parent when it has an active child (light mode)
&.dropdown.has-active-child {
> a {
background-color: color-mix(in srgb, #ffffff 90%, var(--c-primary)) !important;
color: var(--c-primary) !important;
.icon-holder {
color: var(--c-primary) !important;
background-color: color-mix(in srgb, var(--c-primary) 8%, transparent) !important;
}
}
}
a {
&:hover,
&:focus {
background-color: color-mix(in srgb, #ffffff 90%, var(--c-primary));
color: var(--c-primary);
.icon-holder {
color: var(--c-primary);
background-color: color-mix(in srgb, var(--c-primary) 8%, transparent);
}
}
}
}
// Dropdown items for light mode
> li {
&.dropdown {
ul.dropdown-menu {
> li {
&.actived {
a {
color: var(--c-primary) !important;
background-color: color-mix(in srgb, #ffffff 85%, var(--c-primary)) !important;
}
}
> a {
&:hover,
&:focus {
background-color: color-mix(in srgb, #ffffff 90%, var(--c-primary));
color: var(--c-primary);
}
}
}
}
}
}
}
}
}
================================================
FILE: src/assets/styles/spec/components/topbar.scss
================================================
@use 'sass:color';
@use '../settings/baseColors' as *;
@use '../settings/breakpoints' as *;
@use '../tools/mixins/mediaQueriesRanges' as *;
@use '../tools/mixins/clearfix' as *;
@use '../tools/mixins/placeholder' as *;
// ---------------------------------------------------------
// @TOC
// + @Topbar
// + @Collapsed State
// + @Mobile Responsive Fixes
// ---------------------------------------------------------
// @Topbar
// ---------------------------------------------------------
.header {
background-color: var(--c-bkg-card);
border-bottom: 1px solid var(--c-border);
box-shadow: var(--shadow-sm);
display: block;
margin-bottom: 0;
padding: 0;
position: fixed;
transition: all 0.2s ease;
width: calc(100% - #{$offscreen-size});
z-index: 800;
@include to($breakpoint-md) {
width: 100%;
}
@include between($breakpoint-md, $breakpoint-xl) {
width: calc(100% - #{$collapsed-size});
}
.header-container {
@include clearfix;
height: $header-height;
.nav-left,
.nav-right {
list-style: none;
margin-bottom: 0;
padding-left: 0;
position: relative;
> li {
float: left;
> a {
color: var(--c-icon);
display: block;
line-height: $header-height;
min-height: $header-height;
padding: 0 15px;
transition: all 0.2s ease-in-out;
i {
font-size: 17px;
color: var(--c-icon);
}
&:hover,
&:focus {
color: var(--c-icon-hover);
text-decoration: none;
i {
color: var(--c-icon-hover);
}
}
@include to($breakpoint-md) {
padding: 0 15px;
}
}
}
.notifications {
position: relative;
.counter {
background-color: $default-danger;
border-radius: 50px;
color: $default-white;
font-size: 10px;
line-height: 1;
padding: 3px 5.5px;
position: absolute;
right: 6px;
top: 12px;
}
.dropdown-menu {
min-width: 350px;
padding: 0;
@include to($breakpoint-sm) {
max-width: 300px;
}
}
}
}
.dropdown-menu {
// display: block;
margin: 0;
transform-origin: top right;
// transform: scale(0, 0);
transition: transform 0.15s ease-out;
.divider {
border-bottom: 1px solid $border-color;
height: 1px;
overflow: hidden;
}
> li {
> a {
transition: all 0.2s ease-out;
}
}
}
.show {
.dropdown-menu {
transform: scale(1, 1);
}
}
.nav-left {
float: left;
margin-left: 15px;
}
.nav-right {
float: right;
.dropdown-menu {
left: auto;
right: 0;
> li {
width: 100%;
> a {
line-height: 1.5;
min-height: auto;
padding: 10px 15px;
}
}
}
}
}
.search-box {
.search-icon-close {
display: none;
}
&.active {
.search-icon {
display: none;
}
.search-icon-close {
display: inline-block;
}
}
}
.search-input {
display: none;
&.active {
display: inline-block;
}
input {
background-color: transparent;
border: 0;
box-shadow: none;
font-size: 18px;
height: 40px;
margin-top: 12px;
outline: none;
padding: 5px;
@include to($breakpoint-sm) {
width: 85px;
}
@include placeholder {
color: color.adjust($default-text-color, $lightness: 20%);
font-style: italic;
}
}
}
}
// ---------------------------------------------------------
// @Mobile Responsive Fixes - AGGRESSIVE APPROACH
// ---------------------------------------------------------
// Tablet mobile fixes (768px to 991px)
@include to($breakpoint-md) {
.header {
.header-container {
padding: 0 10px;
.nav-left {
margin-left: 5px;
> li > a {
padding: 0 8px !important;
}
}
.nav-right {
margin-right: 5px;
> li {
> a {
padding: 0 8px !important;
// Hide text in user dropdown on tablet
.peer:last-child {
display: none;
}
}
}
// Make theme toggle more compact
.theme-toggle {
padding: 0 5px !important;
.form-check-label {
font-size: 9px !important;
margin: 0 3px !important;
}
.form-check-input {
width: 1.8rem !important;
height: 1rem !important;
}
}
}
}
}
}
// Small mobile phones (576px to 767px)
@include to($breakpoint-sm) {
.header {
.header-container {
height: auto;
min-height: $header-height;
padding: 0 5px;
.nav-left {
margin-left: 2px;
> li > a {
padding: 0 5px !important;
}
// Hide search toggle on small mobile - make it icon only when active
.search-box:not(.active) {
display: none;
}
}
.nav-right {
margin-right: 2px;
> li {
> a {
padding: 0 4px !important;
// Hide all text content, keep only icons
span {
display: none;
}
.peer:last-child {
display: none;
}
}
}
// Ultra-compact theme toggle
.theme-toggle {
padding: 0 3px !important;
.form-check {
flex-direction: column;
align-items: center;
.form-check-label {
font-size: 7px !important;
margin: 0 !important;
line-height: 1 !important;
span {
display: none !important;
}
}
.form-check-input {
width: 1.5rem !important;
height: 0.8rem !important;
margin: 1px 0 !important;
}
}
}
}
// Full-screen mobile dropdowns
.nav-right .dropdown-menu {
position: fixed !important;
top: $header-height !important;
left: 0 !important;
right: 0 !important;
bottom: 0 !important;
width: 100vw !important;
height: calc(100vh - #{$header-height}) !important;
max-width: none !important;
min-width: auto !important;
transform: none !important;
border-radius: 0 !important;
z-index: 9999;
overflow-y: auto;
// Close button for mobile dropdowns
&::before {
content: "✕ Close";
position: sticky;
top: 0;
display: block;
background: var(--c-primary);
color: white;
text-align: center;
padding: 10px;
cursor: pointer;
font-weight: bold;
z-index: 10000;
}
}
}
}
}
// Extra small mobile phones (less than 576px)
@include to(480px) {
.header {
.header-container {
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: nowrap;
padding: 0 2px;
.nav-left {
flex: 0 0 auto;
margin: 0;
> li {
float: none;
display: inline-block;
> a {
padding: 0 3px !important;
font-size: 14px !important;
}
}
// Search overlay for tiny screens
.search-input.active {
position: fixed;
top: $header-height;
left: 0;
right: 0;
background: var(--c-bkg-card);
border-top: 1px solid var(--c-border);
padding: 15px;
z-index: 9998;
input {
width: 100%;
padding: 12px;
font-size: 16px;
border: 1px solid var(--c-border);
border-radius: 6px;
background: var(--c-bkg-card);
color: var(--c-text-base);
margin-top: 0;
&::placeholder {
color: var(--c-text-muted);
}
}
}
}
.nav-right {
flex: 0 0 auto;
margin: 0;
> li {
float: none;
display: inline-block;
> a {
padding: 0 2px !important;
font-size: 12px !important;
}
}
// Minimal theme toggle
.theme-toggle {
padding: 0 1px !important;
.form-check {
.form-check-label {
display: none !important;
}
.form-check-input {
width: 1.2rem !important;
height: 0.7rem !important;
}
}
}
}
}
}
}
// ---------------------------------------------------------
// @Collapsed State
// ---------------------------------------------------------
.is-collapsed {
.header {
width: calc(100% - #{$collapsed-size});
@include to($breakpoint-md) {
width: 100%;
}
@include between($breakpoint-md, $breakpoint-xl) {
width: calc(100% - #{$offscreen-size});
}
}
}
// ---------------------------------------------------------
// @Dark Mode Topbar
// ---------------------------------------------------------
[data-theme="dark"] {
.header {
background-color: var(--c-bkg-card);
border-bottom-color: var(--c-border);
.header-container {
.nav-left,
.nav-right {
> li {
> a {
color: var(--c-icon);
i {
color: var(--c-icon);
}
&:hover,
&:focus {
color: var(--c-icon-hover);
i {
color: var(--c-icon-hover);
}
}
// Active state for hamburger/search when clicked
&:active,
&.active {
color: var(--c-text-base);
i {
color: var(--c-text-base);
}
}
}
}
}
// Search box active state
.search-box {
&.active {
a {
color: var(--c-icon-hover);
i {
color: var(--c-icon-hover);
}
}
}
}
// Dropdown menu dark mode
.dropdown-menu {
background-color: var(--c-bkg-card);
border-color: var(--c-border);
.divider {
border-bottom-color: var(--c-border);
}
> li > a {
color: var(--c-text-muted);
&:hover,
&:focus {
color: var(--c-text-base);
background-color: var(--c-bkg-hover);
}
}
}
}
// Search input dark mode
.search-input {
input {
color: var(--c-text-base);
&::placeholder {
color: var(--c-text-muted);
}
}
}
}
}
================================================
FILE: src/assets/styles/spec/generic/base.scss
================================================
@use '../settings/fonts' as *;
@use '../settings/baseColors' as *;
@use 'sass:color';
html, html a, body {
-webkit-font-smoothing: antialiased;
}
a {
transition: all 0.3s ease-in-out;
text-decoration: none;
}
body {
font-family: $font-primary;
font-size: 14px;
color: $default-text-color;
line-height: 1.5;
letter-spacing: 0.2px;
overflow-x: hidden;
}
h1,
h2,
h3,
h4,
h5,
h6 {
font-family: $font-secondary;
letter-spacing: 0.5px;
line-height: 1.5;
font-weight: 400;
a {
font-family: $font-secondary;
}
small {
font-weight: 300;
color: color.adjust($default-dark, $lightness: 5%);
}
}
p {
font-family: $font-primary;
line-height: 1.9;
}
.lead {
font-size: 18px;
}
ul {
margin-bottom: 0;
}
a {
color: $default-info;
&:hover,
&:focus {
text-decoration: none;
color: darken($default-info, 10%);
}
&:focus {
outline: none;
}
&.text-gray {
&:hover,
&:focus,
&.active {
color: $default-dark !important;
}
}
}
:focus {
outline: none;
}
hr {
border-top: 1px solid $border-color;
}
a.btn {
color:#fff;
}
a.btn:hover {
color:#fff;
}
.btn-color {
color:#fff;
}
.btn-color:hover {
color:#fff;
}
================================================
FILE: src/assets/styles/spec/generic/index.scss
================================================
@use 'base' as *;
================================================
FILE: src/assets/styles/spec/index.scss
================================================
@use 'generic/index' as *;
@use 'components/index' as *;
@use 'screens/index' as *;
@use 'utils/index' as *;
================================================
FILE: src/assets/styles/spec/screens/chat.scss
================================================
@use '../settings/baseColors' as *;
@use '../settings/breakpoints' as *;
@use '../tools/mixins/mediaQueriesRanges' as *;
#chat-sidebar {
width: 250px;
height: calc(100vh - #{$header-height} - 60px);
overflow: auto;
@include to($breakpoint-md) {
transition: all 0.3s ease-in-out;
margin-left: -250px;
&.open {
margin-left: 0;
}
}
}
#chat-box {
height: calc(100vh - #{$header-height} - 60px);
overflow: auto;
}
// Dark mode chat styles
[data-theme="dark"] {
// Chat sidebar search input
input[name="chatSearch"] {
background: var(--c-bkg-card);
color: var(--c-text-base);
border-color: var(--c-border);
&::placeholder {
color: var(--c-text-muted);
}
&:focus {
background: var(--c-bkg-card);
border-color: var(--c-primary);
box-shadow: 0 0 0 0.2rem rgba(var(--c-primary-rgb), 0.25);
}
}
// Contact list items
.peers.bgc-white {
background: var(--c-bkg-card) !important;
border-color: var(--c-border) !important;
&:hover,
&.bgcH-grey-50:hover {
background: var(--c-bkg-hover) !important;
}
h6 {
color: var(--c-text-base);
}
small {
// Status colors remain as they are for visual indication
&.c-grey-500 {
color: var(--c-text-muted) !important;
}
}
}
// Chat header
.peers.bgc-white {
background: var(--c-bkg-card) !important;
h6 {
color: var(--c-text-base);
}
i {
color: var(--c-text-muted);
}
.c-grey-900 {
color: var(--c-text-base) !important;
&.cH-blue-500:hover {
color: var(--c-primary) !important;
}
}
}
// Chat background area
.bgc-grey-200 {
background: var(--c-bkg-body) !important;
}
.bgc-grey-100 {
background: var(--c-bkg-body) !important;
}
// Chat messages
.pY-3.pX-10.bgc-white {
background: var(--c-bkg-card) !important;
color: var(--c-text-base);
border: 1px solid var(--c-border);
small {
color: var(--c-text-muted);
}
span {
color: var(--c-text-base);
}
}
// Chat input area
.bdT.bgc-white {
background: var(--c-bkg-card) !important;
border-color: var(--c-border) !important;
.form-control {
background: var(--c-bkg-body);
border-color: var(--c-border);
color: var(--c-text-base);
&::placeholder {
color: var(--c-text-muted);
}
&:focus {
background: var(--c-bkg-body);
border-color: var(--c-primary);
color: var(--c-text-base);
box-shadow: 0 0 0 0.2rem rgba(var(--c-primary-rgb), 0.25);
}
}
.btn-primary {
background: var(--c-primary);
border-color: var(--c-primary);
&:hover {
background: var(--c-primary-hover);
border-color: var(--c-primary-hover);
}
&:focus {
box-shadow: 0 0 0 0.2rem rgba(var(--c-primary-rgb), 0.25);
}
}
}
}
================================================
FILE: src/assets/styles/spec/screens/email.scss
================================================
@use '../settings/baseColors' as *;
@use '../settings/breakpoints' as *;
@use '../tools/mixins/mediaQueriesRanges' as *;
.email-app {
.email-side-nav {
background: var(--c-bkg-card);
position: fixed;
border-right: 1px solid var(--c-border);
float: left;
width: 250px;
transition: all 0.3s ease-in-out;
@include to($breakpoint-md) {
z-index: 1;
left: -250px;
}
}
.email-wrapper {
margin: 0;
padding: 0;
overflow: auto;
min-height: 100%;
transition: all 0.3s ease-in-out;
@include to($breakpoint-md) {
position: absolute;
left: 0;
width: 100%;
overflow-x: hidden;
}
@include from($breakpoint-md) {
margin-left: 250px;
}
.email-list {
position: relative;
padding: 0;
width: 100%;
overflow-y: hidden;
background-color: var(--c-bkg-card);
height: calc(100vh - #{$header-height});
@include to($breakpoint-md) {
max-height: calc(100vh - 65px);
}
@include from($breakpoint-md) {
width: 40%;
border-right: 1px solid var(--c-border);
float: left;
}
}
.email-content {
float: left;
width: 60%;
position: relative;
padding: 0;
background-color: var(--c-bkg-card);
// min-height: calc(100vh - #{$header-height});
&.no-inbox-view {
width: 100%;
}
@include to($breakpoint-md) {
position: absolute;
top: 0;
left: 100%;
width: 100%;
height: 100%;
transition: all 0.3s ease-in-out;
max-height: calc(100vh - #{$header-height});
overflow-y: scroll;
&.open {
left: 0;
}
}
}
.email-compose {
position: relative;
.email-compose-body {
padding: 30px 20px;
}
}
}
&.side-active {
.email-side-nav {
@include to($breakpoint-md) {
left: 0;
}
}
.email-wrapper {
@include to($breakpoint-md) {
left: 250px;
}
}
}
}
================================================
FILE: src/assets/styles/spec/screens/index.scss
================================================
@use 'chat' as *;
@use 'email' as *;
================================================
FILE: src/assets/styles/spec/settings/baseColors.scss
================================================
@use 'sass:color';
@use 'materialColors' as *;
// ---------------------------------------------------------
// @TOC
// ---------------------------------------------------------
// + @Greyscale Colors
// + @Bootstrap Color System
// + @Default Colors
// + @Inverted Colors
// + @Others
// + @Header Themes
// + @Social Networks Colors
// ---------------------------------------------------------
// @Greyscale Colors
// ---------------------------------------------------------
// Colors below are ordered from lightest to darkest
$grey-100 : #f9fafb;
$grey-200 : #f2f3f5;
$grey-300 : #e6eaf0;
$grey-400 : #d3d9e3;
$grey-500 : #b9c2d0;
$grey-600 : #7c8695;
$grey-700 : #72777a;
$grey-800 : #565a5c;
$grey-900 : #313435;
$grey-colors-alt: (
grey-100 : #f9fafb,
grey-200 : #f2f3f5,
grey-300 : #e6eaf0,
grey-400 : #d3d9e3,
grey-500 : #b9c2d0,
grey-600 : #7c8695,
grey-700 : #72777a,
grey-800 : #565a5c,
grey-900 : #313435,
);
// ---------------------------------------------------------
// @Bootstrap Color System
// ---------------------------------------------------------
$blue : $md-blue-500;
$indigo : $md-indigo-500;
$purple : $md-purple-500;
$pink : $md-pink-500;
$red : $md-red-500;
$orange : $md-orange-500;
$yellow : $md-yellow-500;
$green : $md-green-500;
$teal : $md-teal-500;
$cyan : $md-cyan-500;
// ---------------------------------------------------------
// @Default Colors
// ---------------------------------------------------------
$default-danger : #ff3c7e;
$default-dark : #313435;
$default-grey : #565a5c;
$default-info : #0f9aee;
$default-primary : #7774e7;
$default-success : #37c936;
$default-text-color : #72777a;
$default-warning : #fc0;
$default-white : #fff;
// ---------------------------------------------------------
// @Inverted Colors
// ---------------------------------------------------------
$inverse-danger : color.adjust($default-danger, $lightness: 35%);
$inverse-info : color.adjust($default-info, $lightness: 45%);
$inverse-primary : color.adjust($default-primary, $lightness: 30%);
$inverse-success : color.adjust($default-success, $lightness: 45%);
$inverse-warning : color.adjust($default-warning, $lightness: 45%);
// ---------------------------------------------------------
// @Others
// ---------------------------------------------------------
$border-color : #e6ecf5;
$collapsed-size : 70px;
$header-height : 65px;
$offscreen-size : 280px;
$side-nav-dark : #313644;
$side-nav-dark-border : rgba(120, 130, 140, 0.3);
$side-nav-dark-font : #99abb4;
// ---------------------------------------------------------
// @Header Themes
// ---------------------------------------------------------
$theme-danger : #f53f61;
$theme-dark : color.adjust($side-nav-dark, $lightness: 10%);
$theme-info : $default-info;
$theme-primary : $default-primary;
$theme-success : color.adjust($default-success, $saturation: -5%);
================================================
FILE: src/assets/styles/spec/settings/borders.scss
================================================
// ---------------------------------------------------------
// @Borders
// ---------------------------------------------------------
$border-width: 1px;
$border-color: rgba(0, 0, 0, 0.0625);
================================================
FILE: src/assets/styles/spec/settings/breakpoints.scss
================================================
// ---------------------------------------------------------
// @Breakpoints
// ---------------------------------------------------------
$breakpoint-xl : 1440px;
$breakpoint-lg : 1200px;
$breakpoint-md : 992px;
$breakpoint-sm : 768px;
$breakpoint-xs : 0;
$breakpoints: (
"xl\\+" "screen and (min-width: #{$breakpoint-xl})",
"lg\\+" "screen and (min-width: #{$breakpoint-lg})",
"md\\+" "screen and (min-width: #{$breakpoint-md})",
"sm\\+" "screen and (min-width: #{$breakpoint-sm})",
"xs\\+" "screen and (min-width: #{$breakpoint-xs})",
"xl-" "screen and (max-width: #{$breakpoint-xl - 1px})",
"lg-" "screen and (max-width: #{$breakpoint-lg - 1px})",
"md-" "screen and (max-width: #{$breakpoint-md - 1px})",
"sm-" "screen and (max-width: #{$breakpoint-sm - 1px})",
"lg" "screen and (min-width: #{$breakpoint-lg - 1px}) and (max-width: #{$breakpoint-xl - 1px})",
"md" "screen and (min-width: #{$breakpoint-md - 1px}) and (max-width: #{$breakpoint-lg - 1px})",
"sm" "screen and (min-width: #{$breakpoint-sm - 1px}) and (max-width: #{$breakpoint-md - 1px})",
);
================================================
FILE: src/assets/styles/spec/settings/fonts.scss
================================================
$font-primary:
Roboto, -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Arial, sans-serif;
$font-secondary: $font-primary;
$font-size-base: 0.875rem;
================================================
FILE: src/assets/styles/spec/settings/index.scss
================================================
@use 'breakpoints' as *;
@use 'brand-colors/dist/latest/scss/brand-colors.latest.scss' as *;
@use 'baseColors' as *;
@use 'borders' as *;
================================================
FILE: src/assets/styles/spec/settings/materialColors.scss
================================================
$md-red-50 : #ffebee;
$md-red-100 : #ffcdd2;
$md-red-200 : #ef9a9a;
$md-red-300 : #e57373;
$md-red-400 : #ef5350;
$md-red-500 : #f44336;
$md-red-600 : #e53935;
$md-red-700 : #d32f2f;
$md-red-800 : #c62828;
$md-red-900 : #b71c1c;
$md-red-a100 : #ff8a80;
$md-red-a200 : #ff5252;
$md-red-a400 : #ff1744;
$md-red-a700 : #d50000;
$md-pink-50 : #fce4ec;
$md-pink-100 : #f8bbd0;
$md-pink-200 : #f48fb1;
$md-pink-300 : #f06292;
$md-pink-400 : #ec407a;
$md-pink-500 : #e91e63;
$md-pink-600 : #d81b60;
$md-pink-700 : #c2185b;
$md-pink-800 : #ad1457;
$md-pink-900 : #880e4f;
$md-pink-a100 : #ff80ab;
$md-pink-a200 : #ff4081;
$md-pink-a400 : #f50057;
$md-pink-a700 : #c51162;
$md-purple-50 : #f3e5f5;
$md-purple-100 : #e1bee7;
$md-purple-200 : #ce93d8;
$md-purple-300 : #ba68c8;
$md-purple-400 : #ab47bc;
$md-purple-500 : #9c27b0;
$md-purple-600 : #8e24aa;
$md-purple-700 : #7b1fa2;
$md-purple-800 : #6a1b9a;
$md-purple-900 : #4a148c;
$md-purple-a100 : #ea80fc;
$md-purple-a200 : #e040fb;
$md-purple-a400 : #d500f9;
$md-purple-a700 : #aa00ff;
$md-deep-purple-50 : #ede7f6;
$md-deep-purple-100 : #d1c4e9;
$md-deep-purple-200 : #b39ddb;
$md-deep-purple-300 : #9575cd;
$md-deep-purple-400 : #7e57c2;
$md-deep-purple-500 : #673ab7;
$md-deep-purple-600 : #5e35b1;
$md-deep-purple-700 : #512da8;
$md-deep-purple-800 : #4527a0;
$md-deep-purple-900 : #311b92;
$md-deep-purple-a100 : #b388ff;
$md-deep-purple-a200 : #7c4dff;
$md-deep-purple-a400 : #651fff;
$md-deep-purple-a700 : #6200ea;
$md-indigo-50 : #e8eaf6;
$md-indigo-100 : #c5cae9;
$md-indigo-200 : #9fa8da;
$md-indigo-300 : #7986cb;
$md-indigo-400 : #5c6bc0;
$md-indigo-500 : #3f51b5;
$md-indigo-600 : #3949ab;
$md-indigo-700 : #303f9f;
$md-indigo-800 : #283593;
$md-indigo-900 : #1a237e;
$md-indigo-a100 : #8c9eff;
$md-indigo-a200 : #536dfe;
$md-indigo-a400 : #3d5afe;
$md-indigo-a700 : #304ffe;
$md-blue-50 : #e3f2fd;
$md-blue-100 : #bbdefb;
$md-blue-200 : #90caf9;
$md-blue-300 : #64b5f6;
$md-blue-400 : #42a5f5;
$md-blue-500 : #2196f3;
$md-blue-600 : #1e88e5;
$md-blue-700 : #1976d2;
$md-blue-800 : #1565c0;
$md-blue-900 : #0d47a1;
$md-blue-a100 : #82b1ff;
$md-blue-a200 : #448aff;
$md-blue-a400 : #2979ff;
$md-blue-a700 : #2962ff;
$md-light-blue-50 : #e1f5fe;
$md-light-blue-100 : #b3e5fc;
$md-light-blue-200 : #81d4fa;
$md-light-blue-300 : #4fc3f7;
$md-light-blue-400 : #29b6f6;
$md-light-blue-500 : #03a9f4;
$md-light-blue-600 : #039be5;
$md-light-blue-700 : #0288d1;
$md-light-blue-800 : #0277bd;
$md-light-blue-900 : #01579b;
$md-light-blue-a100 : #80d8ff;
$md-light-blue-a200 : #40c4ff;
$md-light-blue-a400 : #00b0ff;
$md-light-blue-a700 : #0091ea;
$md-cyan-50 : #e0f7fa;
$md-cyan-100 : #b2ebf2;
$md-cyan-200 : #80deea;
$md-cyan-300 : #4dd0e1;
$md-cyan-400 : #26c6da;
$md-cyan-500 : #00bcd4;
$md-cyan-600 : #00acc1;
$md-cyan-700 : #0097a7;
$md-cyan-800 : #00838f;
$md-cyan-900 : #006064;
$md-cyan-a100 : #84ffff;
$md-cyan-a200 : #18ffff;
$md-cyan-a400 : #00e5ff;
$md-cyan-a700 : #00b8d4;
$md-teal-50 : #e0f2f1;
$md-teal-100 : #b2dfdb;
$md-teal-200 : #80cbc4;
$md-teal-300 : #4db6ac;
$md-teal-400 : #26a69a;
$md-teal-500 : #009688;
$md-teal-600 : #00897b;
$md-teal-700 : #00796b;
$md-teal-800 : #00695c;
$md-teal-900 : #004d40;
$md-teal-a100 : #a7ffeb;
$md-teal-a200 : #64ffda;
$md-teal-a400 : #1de9b6;
$md-teal-a700 : #00bfa5;
$md-green-50 : #e8f5e9;
$md-green-100 : #c8e6c9;
$md-green-200 : #a5d6a7;
$md-green-300 : #81c784;
$md-green-400 : #66bb6a;
$md-green-500 : #4caf50;
$md-green-600 : #43a047;
$md-green-700 : #388e3c;
$md-green-800 : #2e7d32;
$md-green-900 : #1b5e20;
$md-green-a100 : #b9f6ca;
$md-green-a200 : #69f0ae;
$md-green-a400 : #00e676;
$md-green-a700 : #00c853;
$md-light-green-50 : #f1f8e9;
$md-light-green-100 : #dcedc8;
$md-light-green-200 : #c5e1a5;
$md-light-green-300 : #aed581;
$md-light-green-400 : #9ccc65;
$md-light-green-500 : #8bc34a;
$md-light-green-600 : #7cb342;
$md-light-green-700 : #689f38;
$md-light-green-800 : #558b2f;
$md-light-green-900 : #33691e;
$md-light-green-a100 : #ccff90;
$md-light-green-a200 : #b2ff59;
$md-light-green-a400 : #76ff03;
$md-light-green-a700 : #64dd17;
$md-lime-50 : #f9fbe7;
$md-lime-100 : #f0f4c3;
$md-lime-200 : #e6ee9c;
$md-lime-300 : #dce775;
$md-lime-400 : #d4e157;
$md-lime-500 : #cddc39;
$md-lime-600 : #c0ca33;
$md-lime-700 : #afb42b;
$md-lime-800 : #9e9d24;
$md-lime-900 : #827717;
$md-lime-a100 : #f4ff81;
$md-lime-a200 : #eeff41;
$md-lime-a400 : #c6ff00;
$md-lime-a700 : #aeea00;
$md-yellow-50 : #fffde7;
$md-yellow-100 : #fff9c4;
$md-yellow-200 : #fff59d;
$md-yellow-300 : #fff176;
$md-yellow-400 : #ffee58;
$md-yellow-500 : #ffeb3b;
$md-yellow-600 : #fdd835;
$md-yellow-700 : #fbc02d;
$md-yellow-800 : #f9a825;
$md-yellow-900 : #f57f17;
$md-yellow-a100 : #ffff8d;
$md-yellow-a200 : #ffff00;
$md-yellow-a400 : #ffea00;
$md-yellow-a700 : #ffd600;
$md-amber-50 : #fff8e1;
$md-amber-100 : #ffecb3;
$md-amber-200 : #ffe082;
$md-amber-300 : #ffd54f;
$md-amber-400 : #ffca28;
$md-amber-500 : #ffc107;
$md-amber-600 : #ffb300;
$md-amber-700 : #ffa000;
$md-amber-800 : #ff8f00;
$md-amber-900 : #ff6f00;
$md-amber-a100 : #ffe57f;
$md-amber-a200 : #ffd740;
$md-amber-a400 : #ffc400;
$md-amber-a700 : #ffab00;
$md-orange-50 : #fff3e0;
$md-orange-100 : #ffe0b2;
$md-orange-200 : #ffcc80;
$md-orange-300 : #ffb74d;
$md-orange-400 : #ffa726;
$md-orange-500 : #ff9800;
$md-orange-600 : #fb8c00;
$md-orange-700 : #f57c00;
$md-orange-800 : #ef6c00;
$md-orange-900 : #e65100;
$md-orange-a100 : #ffd180;
$md-orange-a200 : #ffab40;
$md-orange-a400 : #ff9100;
$md-orange-a700 : #ff6d00;
$md-deep-orange-50 : #fbe9e7;
$md-deep-orange-100 : #ffccbc;
$md-deep-orange-200 : #ffab91;
$md-deep-orange-300 : #ff8a65;
$md-deep-orange-400 : #ff7043;
$md-deep-orange-500 : #ff5722;
$md-deep-orange-600 : #f4511e;
$md-deep-orange-700 : #e64a19;
$md-deep-orange-800 : #d84315;
$md-deep-orange-900 : #bf360c;
$md-deep-orange-a100 : #ff9e80;
$md-deep-orange-a200 : #ff6e40;
$md-deep-orange-a400 : #ff3d00;
$md-deep-orange-a700 : #dd2c00;
$md-brown-50 : #efebe9;
$md-brown-100 : #d7ccc8;
$md-brown-200 : #bcaaa4;
$md-brown-300 : #a1887f;
$md-brown-400 : #8d6e63;
$md-brown-500 : #795548;
$md-brown-600 : #6d4c41;
$md-brown-700 : #5d4037;
$md-brown-800 : #4e342e;
$md-brown-900 : #3e2723;
$md-grey-50 : #fafafa;
$md-grey-100 : #f5f5f5;
$md-grey-200 : #eeeeee;
$md-grey-300 : #e0e0e0;
$md-grey-400 : #bdbdbd;
$md-grey-500 : #9e9e9e;
$md-grey-600 : #757575;
$md-grey-700 : #616161;
$md-grey-800 : #424242;
$md-grey-900 : #212121;
$md-blue-grey-50 : #eceff1;
$md-blue-grey-100 : #cfd8dc;
$md-blue-grey-200 : #b0bec5;
$md-blue-grey-300 : #90a4ae;
$md-blue-grey-400 : #78909c;
$md-blue-grey-500 : #607d8b;
$md-blue-grey-600 : #546e7a;
$md-blue-grey-700 : #455a64;
$md-blue-grey-800 : #37474f;
$md-blue-grey-900 : #263238;
$md-dark-text-primary : rgba(0, 0, 0, 0.87);
$md-dark-text-secondary : rgba(0, 0, 0, 0.54);
$md-dark-text-disabled : rgba(0, 0, 0, 0.38);
$md-dark-text-dividers : rgba(0, 0, 0, 0.12);
$md-light-text-primary : rgba(255, 255, 255, 1);
$md-light-text-secondary : rgba(255, 255, 255, 0.7);
$md-light-text-disabled : rgba(255, 255, 255, 0.5);
$md-light-text-dividers : rgba(255, 255, 255, 0.12);
$md-dark-icons-active : rgba(0, 0, 0, 0.54);
$md-dark-icons-inactive : rgba(0, 0, 0, 0.38);
$md-light-icons-active : rgba(255, 255, 255, 1);
$md-light-icons-inactive : rgba(255, 255, 255, 0.5);
$md-white : #ffffff;
$md-black : #000000;
$md-colors: (
white : #ffffff,
red-50 : #ffebee,
red-100 : #ffcdd2,
red-200 : #ef9a9a,
red-300 : #e57373,
red-400 : #ef5350,
red-500 : #f44336,
red-600 : #e53935,
red-700 : #d32f2f,
red-800 : #c62828,
red-900 : #b71c1c,
red-a100 : #ff8a80,
red-a200 : #ff5252,
red-a400 : #ff1744,
red-a700 : #d50000,
pink-50 : #fce4ec,
pink-100 : #f8bbd0,
pink-200 : #f48fb1,
pink-300 : #f06292,
pink-400 : #ec407a,
pink-500 : #e91e63,
pink-600 : #d81b60,
pink-700 : #c2185b,
pink-800 : #ad1457,
pink-900 : #880e4f,
pink-a100 : #ff80ab,
pink-a200 : #ff4081,
pink-a400 : #f50057,
pink-a700 : #c51162,
purple-50 : #f3e5f5,
purple-100 : #e1bee7,
purple-200 : #ce93d8,
purple-300 : #ba68c8,
purple-400 : #ab47bc,
purple-500 : #9c27b0,
purple-600 : #8e24aa,
purple-700 : #7b1fa2,
purple-800 : #6a1b9a,
purple-900 : #4a148c,
purple-a100 : #ea80fc,
purple-a200 : #e040fb,
purple-a400 : #d500f9,
purple-a700 : #aa00ff,
deep-purple-50 : #ede7f6,
deep-purple-100 : #d1c4e9,
deep-purple-200 : #b39ddb,
deep-purple-300 : #9575cd,
deep-purple-400 : #7e57c2,
deep-purple-500 : #673ab7,
deep-purple-600 : #5e35b1,
deep-purple-700 : #512da8,
deep-purple-800 : #4527a0,
deep-purple-900 : #311b92,
deep-purple-a100 : #b388ff,
deep-purple-a200 : #7c4dff,
deep-purple-a400 : #651fff,
deep-purple-a700 : #6200ea,
indigo-50 : #e8eaf6,
indigo-100 : #c5cae9,
indigo-200 : #9fa8da,
indigo-300 : #7986cb,
indigo-400 : #5c6bc0,
indigo-500 : #3f51b5,
indigo-600 : #3949ab,
indigo-700 : #303f9f,
indigo-800 : #283593,
indigo-900 : #1a237e,
indigo-a100 : #8c9eff,
indigo-a200 : #536dfe,
indigo-a400 : #3d5afe,
indigo-a700 : #304ffe,
blue-50 : #e3f2fd,
blue-100 : #bbdefb,
blue-200 : #90caf9,
blue-300 : #64b5f6,
blue-400 : #42a5f5,
blue-500 : #2196f3,
blue-600 : #1e88e5,
blue-700 : #1976d2,
blue-800 : #1565c0,
blue-900 : #0d47a1,
blue-a100 : #82b1ff,
blue-a200 : #448aff,
blue-a400 : #2979ff,
blue-a700 : #2962ff,
light-blue-50 : #e1f5fe,
light-blue-100 : #b3e5fc,
light-blue-200 : #81d4fa,
light-blue-300 : #4fc3f7,
light-blue-400 : #29b6f6,
light-blue-500 : #03a9f4,
light-blue-600 : #039be5,
light-blue-700 : #0288d1,
light-blue-800 : #0277bd,
light-blue-900 : #01579b,
light-blue-a100 : #80d8ff,
light-blue-a200 : #40c4ff,
light-blue-a400 : #00b0ff,
light-blue-a700 : #0091ea,
cyan-50 : #e0f7fa,
cyan-100 : #b2ebf2,
cyan-200 : #80deea,
cyan-300 : #4dd0e1,
cyan-400 : #26c6da,
cyan-500 : #00bcd4,
cyan-600 : #00acc1,
cyan-700 : #0097a7,
cyan-800 : #00838f,
cyan-900 : #006064,
cyan-a100 : #84ffff,
cyan-a200 : #18ffff,
cyan-a400 : #00e5ff,
cyan-a700 : #00b8d4,
teal-50 : #e0f2f1,
teal-100 : #b2dfdb,
teal-200 : #80cbc4,
teal-300 : #4db6ac,
teal-400 : #26a69a,
teal-500 : #009688,
teal-600 : #00897b,
teal-700 : #00796b,
teal-800 : #00695c,
teal-900 : #004d40,
teal-a100 : #a7ffeb,
teal-a200 : #64ffda,
teal-a400 : #1de9b6,
teal-a700 : #00bfa5,
green-50 : #e8f5e9,
green-100 : #c8e6c9,
green-200 : #a5d6a7,
green-300 : #81c784,
green-400 : #66bb6a,
green-500 : #4caf50,
green-600 : #43a047,
green-700 : #388e3c,
green-800 : #2e7d32,
green-900 : #1b5e20,
green-a100 : #b9f6ca,
green-a200 : #69f0ae,
green-a400 : #00e676,
green-a700 : #00c853,
light-green-50 : #f1f8e9,
light-green-100 : #dcedc8,
light-green-200 : #c5e1a5,
light-green-300 : #aed581,
light-green-400 : #9ccc65,
light-green-500 : #8bc34a,
light-green-600 : #7cb342,
light-green-700 : #689f38,
light-green-800 : #558b2f,
light-green-900 : #33691e,
light-green-a100 : #ccff90,
light-green-a200 : #b2ff59,
light-green-a400 : #76ff03,
light-green-a700 : #64dd17,
lime-50 : #f9fbe7,
lime-100 : #f0f4c3,
lime-200 : #e6ee9c,
lime-300 : #dce775,
lime-400 : #d4e157,
lime-500 : #cddc39,
lime-600 : #c0ca33,
lime-700 : #afb42b,
lime-800 : #9e9d24,
lime-900 : #827717,
lime-a100 : #f4ff81,
lime-a200 : #eeff41,
lime-a400 : #c6ff00,
lime-a700 : #aeea00,
yellow-50 : #fffde7,
yellow-100 : #fff9c4,
yellow-200 : #fff59d,
yellow-300 : #fff176,
yellow-400 : #ffee58,
yellow-500 : #ffeb3b,
yellow-600 : #fdd835,
yellow-700 : #fbc02d,
yellow-800 : #f9a825,
yellow-900 : #f57f17,
yellow-a100 : #ffff8d,
yellow-a200 : #ffff00,
yellow-a400 : #ffea00,
yellow-a700 : #ffd600,
amber-50 : #fff8e1,
amber-100 : #ffecb3,
amber-200 : #ffe082,
amber-300 : #ffd54f,
amber-400 : #ffca28,
amber-500 : #ffc107,
amber-600 : #ffb300,
amber-700 : #ffa000,
amber-800 : #ff8f00,
amber-900 : #ff6f00,
amber-a100 : #ffe57f,
amber-a200 : #ffd740,
amber-a400 : #ffc400,
amber-a700 : #ffab00,
orange-50 : #fff3e0,
orange-100 : #ffe0b2,
orange-200 : #ffcc80,
orange-300 : #ffb74d,
orange-400 : #ffa726,
orange-500 : #ff9800,
orange-600 : #fb8c00,
orange-700 : #f57c00,
orange-800 : #ef6c00,
orange-900 : #e65100,
orange-a100 : #ffd180,
orange-a200 : #ffab40,
orange-a400 : #ff9100,
orange-a700 : #ff6d00,
deep-orange-50 : #fbe9e7,
deep-orange-100 : #ffccbc,
deep-orange-200 : #ffab91,
deep-orange-300 : #ff8a65,
deep-orange-400 : #ff7043,
deep-orange-500 : #ff5722,
deep-orange-600 : #f4511e,
deep-orange-700 : #e64a19,
deep-orange-800 : #d84315,
deep-orange-900 : #bf360c,
deep-orange-a100 : #ff9e80,
deep-orange-a200 : #ff6e40,
deep-orange-a400 : #ff3d00,
deep-orange-a700 : #dd2c00,
brown-50 : #efebe9,
brown-100 : #d7ccc8,
brown-200 : #bcaaa4,
brown-300 : #a1887f,
brown-400 : #8d6e63,
brown-500 : #795548,
brown-600 : #6d4c41,
brown-700 : #5d4037,
brown-800 : #4e342e,
brown-900 : #3e2723,
grey-50 : #fafafa,
grey-100 : #f5f5f5,
grey-200 : #eeeeee,
grey-300 : #e0e0e0,
grey-400 : #bdbdbd,
grey-500 : #9e9e9e,
grey-600 : #757575,
grey-700 : #616161,
grey-800 : #424242,
grey-900 : #212121,
blue-grey-50 : #eceff1,
blue-grey-100 : #cfd8dc,
blue-grey-200 : #b0bec5,
blue-grey-300 : #90a4ae,
blue-grey-400 : #78909c,
blue-grey-500 : #607d8b,
blue-grey-600 : #546e7a,
blue-grey-700 : #455a64,
blue-grey-800 : #37474f,
blue-grey-900 : #263238,
);
================================================
FILE: src/assets/styles/spec/tools/index.scss
================================================
@use 'mixins/index' as *;
================================================
FILE: src/assets/styles/spec/tools/mixins/clearfix.scss
================================================
//----------------------------------------------------------
// @Clearfix
//----------------------------------------------------------
@mixin clearfix {
&::before,
&::after {
content: ' ';
display: table;
}
&::after {
clear: both;
}
}
================================================
FILE: src/assets/styles/spec/tools/mixins/index.scss
================================================
@use 'placeholder' as *;
@use 'clearfix' as *;
@use 'mediaQueriesRanges' as *;
================================================
FILE: src/assets/styles/spec/tools/mixins/mediaQueriesRanges.scss
================================================
// ---------------------------------------------------------
// @TOC
// ---------------------------------------------------------
// + @General Media Query
// + @All Above Media Query
// + @All Under Media Query
// + @Between Two Devices Media Query
// ---------------------------------------------------------
// @General Media Query Mixin
// ---------------------------------------------------------
// Mixin used for custom rules that don't follow
// any of the following premade media queries.
@mixin mq($condition) {
@media #{$condition} {
@content;
}
}
// ---------------------------------------------------------
// @All Above Media Query Mixin
// ---------------------------------------------------------
// Mixin used to match certain breakpoint
// and all devices above it.
@mixin from($breakpoint) {
@media screen and (min-width: $breakpoint){
@content;
}
}
// ---------------------------------------------------------
// @All Under Media Query Mixin
// ---------------------------------------------------------
// Mixin used to match all devices under certain breakpoint.
@mixin to($breakpoint) {
@media screen and (max-width: $breakpoint - 1px) {
@content;
}
}
// ---------------------------------------------------------
// @Between Two Devices Media Query Mixin
// ---------------------------------------------------------
// Mixin used to match the devices between 2 breakpoints.
@mixin between($start, $end){
@media screen and (min-width: $start) and (max-width: $end - 1px) {
@content;
}
}
================================================
FILE: src/assets/styles/spec/tools/mixins/placeholder.scss
================================================
//----------------------------------------------------------
// @Placeholder
//----------------------------------------------------------
@mixin placeholder {
&::-webkit-input-placeholder { @content; }
&:-moz-placeholder { @content; }
&::-moz-placeholder { @content; }
&:-ms-input-placeholder { @content; }
}
================================================
FILE: src/assets/styles/spec/utils/colors.scss
================================================
@use '../settings/materialColors' as *;
@use '../settings/baseColors' as *;
// ---------------------------------------------------------
// @TOC
// ---------------------------------------------------------
// + @Material Color
// + @Grey Colors
// ---------------------------------------------------------
// @Material Color
// ---------------------------------------------------------
@each $item, $color in $md-colors {
.c-#{"" + $item}, .cH-#{"" + $item}:hover { color: $color !important; }
.bgc-#{"" + $item}, .bgcH-#{"" + $item}:hover { background-color: $color !important; }
.bdc-#{"" + $item}, .bdcH-#{"" + $item}:hover { border-color: $color !important; }
.fill-#{"" + $item}, .fillH-#{"" + $item}:hover { fill: $color !important; }
.str-#{"" + $item}, .strH-#{"" + $item}:hover { stroke: $color !important; }
}
// ---------------------------------------------------------
// @Grey Colors
// ---------------------------------------------------------
@each $item, $color in $grey-colors-alt {
.c-#{"" + $item}, .cH-#{"" + $item}:hover { color: $color !important; }
.bgc-#{"" + $item}, .bgcH-#{"" + $item}:hover { background-color: $color !important; }
.bdc-#{"" + $item}, .bdcH-#{"" + $item}:hover { border-color: $color !important; }
.fill-#{"" + $item}, .fillH-#{"" + $item}:hover { fill: $color !important; }
.str-#{"" + $item}, .strH-#{"" + $item}:hover { stroke: $color !important; }
}
================================================
FILE: src/assets/styles/spec/utils/index.scss
================================================
@use 'layout/index' as *;
@use 'colors' as *;
================================================
FILE: src/assets/styles/spec/utils/layout/helpers/border.scss
================================================
@use '../../../settings/baseColors' as colors;
@use '../../../settings/borders' as borders;
// ---------------------------------------------------------
// @TOC
// ---------------------------------------------------------
// + @Quick Border Helpers
// + @Border Width
// + @Border Radius
// + @Border Style
// ---------------------------------------------------------
// @Quick Border Helpers
// ---------------------------------------------------------
.bd { border: #{borders.$border-width} solid #{colors.$border-color} !important; }
.bdT { border-top: #{borders.$border-width} solid #{colors.$border-color} !important; }
.bdR { border-right: #{borders.$border-width} solid #{colors.$border-color} !important; }
.bdB { border-bottom: #{borders.$border-width} solid #{colors.$border-color} !important; }
.bdL { border-left: #{borders.$border-width} solid #{colors.$border-color} !important; }
// ---------------------------------------------------------
// @Border Width
// ---------------------------------------------------------
@for $i from 0 through 5 {
.bdw-#{$i} { border-width: #{$i}px !important; }
.bdwT-#{$i} { border-top-width: #{$i}px !important; }
.bdwR-#{$i} { border-right-width: #{$i}px !important; }
.bdwB-#{$i} { border-bottom-width: #{$i}px !important; }
.bdwL-#{$i} { border-left-width: #{$i}px !important; }
}
// ---------------------------------------------------------
// @Border Radius
// ---------------------------------------------------------
@for $i from 0 to 5 {
.bdrs-#{$i} { border-radius: #{$i}px !important; }
.bdrsT-#{$i} {
border-top-left-radius: #{$i}px !important;
border-top-right-radius: #{$i}px !important;
}
.bdrsR-#{$i} {
border-top-right-radius: #{$i}px !important;
border-bottom-right-radius: #{$i}px !important;
}
.bdrsB-#{$i} {
border-bottom-left-radius: #{$i}px !important;
border-bottom-right-radius: #{$i}px !important;
}
.bdrsL-#{$i} {
border-top-left-radius: #{$i}px !important;
border-bottom-left-radius: #{$i}px !important;
}
}
.bdrs-50p { border-radius: 50% !important; }
.bdrs-10em { border-radius: 10em !important; }
// ---------------------------------------------------------
// @Border Style
// ---------------------------------------------------------
.bds-n { border-style: none !important; }
.bds-s { border-style: solid !important; }
.bds-dt { border-style: dotted !important; }
.bds-ds { border-style: dashed !important; }
.bds-db { border-style: double !important; }
.bds-g { border-style: groove !important; }
.bds-r { border-style: ridge !important; }
.bds-i { border-style: inset !important; }
.bds-o { border-style: outset !important; }
================================================
FILE: src/assets/styles/spec/utils/layout/helpers/flex.scss
================================================
@use '../mixins/generateResponsive' as *;
// ---------------------------------------------------------
// @TOC
// ---------------------------------------------------------
// + @Variables
// + @Flex Wrap
// + @Flex Direction
// + @Flex
// + @Flex Basis
// + @Flex Grow
// + @Flex Shrink
// + @Flex Order
// ---------------------------------------------------------
// @Variables
// ---------------------------------------------------------
$responsive: true;
// ---------------------------------------------------------
// @Display
// ---------------------------------------------------------
.d-f { display: flex; }
.d-if { display: inline-flex; }
@if ($responsive == true) {
@include generateResponsive() {
.d-f\@#{$breakpointAlias} { display: flex; }
.d-if\@#{$breakpointAlias} { display: inline-flex; }
}
}
// ---------------------------------------------------------
// @Flex Wrap
// ---------------------------------------------------------
.fxw-w { flex-wrap: wrap; }
.fxw-wr { flex-wrap: wrap-reverse; }
.fxw-nw { flex-wrap: nowrap; }
@if ($responsive == true) {
@include generateResponsive() {
.fxw-w\@#{$breakpointAlias} { flex-wrap: wrap; }
.fxw-wr\@#{$breakpointAlias} { flex-wrap: wrap-reverse; }
.fxw-nw\@#{$breakpointAlias} { flex-wrap: nowrap; }
}
}
// ---------------------------------------------------------
// @Flex Direction
// ---------------------------------------------------------
.fxd-c { flex-direction: column; }
.fxd-cr { flex-direction: column-reverse; }
.fxd-r { flex-direction: row; }
.fxd-rr { flex-direction: row-reverse; }
@if ($responsive == true) {
@include generateResponsive() {
.fxd-c\@#{$breakpointAlias} { flex-direction: column; }
.fxd-cr\@#{$breakpointAlias} { flex-direction: column-reverse; }
.fxd-r\@#{$breakpointAlias} { flex-direction: row; }
.fxd-rr\@#{$breakpointAlias} { flex-direction: row-reverse; }
}
}
// ---------------------------------------------------------
// @Align Items
// ---------------------------------------------------------
.ai-fs { align-items: flex-start; }
.ai-fe { align-items: flex-end; }
.ai-c { align-items: center; }
.ai-b { align-items: baseline; }
.ai-s { align-items: stretch; }
@if ($responsive == true) {
@include generateResponsive() {
.ai-fs\@#{$breakpointAlias} { align-items: flex-start; }
.ai-fe\@#{$breakpointAlias} { align-items: flex-end; }
.ai-c\@#{$breakpointAlias} { align-items: center; }
.ai-b\@#{$breakpointAlias} { align-items: baseline; }
.ai-s\@#{$breakpointAlias} { align-items: stretch; }
}
}
// ---------------------------------------------------------
// @Align Self
// ---------------------------------------------------------
.as-fs { align-self: flex-start; }
.as-fe { align-self: flex-end; }
.as-c { align-self: center; }
.as-b { align-self: baseline; }
.as-s { align-self: stretch; }
@if ($responsive == true) {
@include generateResponsive() {
.as-fs\@#{$breakpointAlias} { align-self: flex-start; }
.as-fe\@#{$breakpointAlias} { align-self: flex-end; }
.as-c\@#{$breakpointAlias} { align-self: center; }
.as-b\@#{$breakpointAlias} { align-self: baseline; }
.as-s\@#{$breakpointAlias} { align-self: stretch; }
}
}
// ---------------------------------------------------------
// @Align Content
// ---------------------------------------------------------
.ac-fs { align-content: flex-start; }
.ac-fe { align-content: flex-end; }
.ac-c { align-content: center; }
.ac-s { align-content: stretch; }
.ac-sb { align-content: space-between; }
.ac-sa { align-content: space-around; }
@if ($responsive == true) {
@include generateResponsive() {
.ac-fs\@#{$breakpointAlias} { align-content: flex-start; }
.ac-fe\@#{$breakpointAlias} { align-content: flex-end; }
.ac-c\@#{$breakpointAlias} { align-content: center; }
.ac-s\@#{$breakpointAlias} { align-content: stretch; }
.ac-sb\@#{$breakpointAlias} { align-content: space-between; }
.ac-sa\@#{$breakpointAlias} { align-content: space-around; }
}
}
// ---------------------------------------------------------
// @Justify Content
// ---------------------------------------------------------
.jc-fs { justify-content: flex-start; }
.jc-fe { justify-content: flex-end; }
.jc-c { justify-content: center; }
.jc-sb { justify-content: space-between; }
.jc-sa { justify-content: space-around; }
@if ($responsive == true) {
@include generateResponsive() {
.jc-fs\@#{$breakpointAlias} { justify-content: flex-start; }
.jc-fe\@#{$breakpointAlias} { justify-content: flex-end; }
.jc-c\@#{$breakpointAlias} { justify-content: center; }
.jc-sb\@#{$breakpointAlias} { justify-content: space-between; }
.jc-sa\@#{$breakpointAlias} { justify-content: space-around; }
}
}
// ---------------------------------------------------------
// @Flex
// ---------------------------------------------------------
.fx-n { flex: none; }
.fx-1 { flex: 1; }
@if ($responsive == true) {
@include generateResponsive() {
.fx-n\@#{$breakpointAlias} { flex: none; }
.fx-1\@#{$breakpointAlias} { flex: 1; }
}
}
// ---------------------------------------------------------
// @Flex Basis
// ---------------------------------------------------------
.fxb-a { flex-basis: auto; }
.fxb-0 { flex-basis: 0; }
@if ($responsive == true) {
@include generateResponsive() {
.fxb-a\@#{$breakpointAlias} { flex-basis: auto; }
.fxb-0\@#{$breakpointAlias} { flex-basis: 0; }
}
}
// ---------------------------------------------------------
// @Flex Grow
// ---------------------------------------------------------
.fxg-1 { flex-grow: 1; }
.fxg-0 { flex-grow: 0; }
@if ($responsive == true) {
@include generateResponsive() {
.fxg-1\@#{$breakpointAlias} { flex-grow: 1; }
.fxg-0\@#{$breakpointAlias} { flex-grow: 0; }
}
}
// ---------------------------------------------------------
// @Flex Shrink
// ---------------------------------------------------------
.fxs-1 { flex-shrink: 1; }
.fxs-0 { flex-shrink: 0; }
@if ($responsive == true) {
@include generateResponsive() {
.fxs-1\@#{$breakpointAlias} { flex-shrink: 1; }
.fxs-0\@#{$breakpointAlias} { flex-shrink: 0; }
}
}
// ---------------------------------------------------------
// @Flex Order
// ---------------------------------------------------------
@for $i from 0 through 12 {
.ord-#{$i} { order: $i; }
@if ($responsive == true) {
@include generateResponsive() {
.ord-#{$i}\@#{$breakpointAlias} { order: $i; }
}
}
}
================================================
FILE: src/assets/styles/spec/utils/layout/helpers/index.scss
================================================
@use 'flex' as *;
@use 'layout' as *;
@use 'lists' as *;
@use 'margin' as *;
@use 'objects' as *;
@use 'padding' as *;
@use 'positions' as *;
@use 'sizes' as *;
@use 'typography' as *;
@use 'border' as *;
@use 'pseudo' as *;
================================================
FILE: src/assets/styles/spec/utils/layout/helpers/layout.scss
================================================
@use '../mixins/generateResponsive' as *;
// ---------------------------------------------------------
// @TOC
// ---------------------------------------------------------
// + @Variables
// + @Display
// + @Overflow
// + @Float
// + @Vertical Align
// + @Position
// + @Z-Index
// ---------------------------------------------------------
// @Variables
// ---------------------------------------------------------
$responsive: true;
// ---------------------------------------------------------
// @Display
// ---------------------------------------------------------
.d-b { display: block !important; }
.d-ib { display: inline-block !important; }
.d-i { display: inline !important; }
.d-tb { display: table !important; }
.d-tbc { display: table-cell !important; }
.d-n { display: none !important; }
@if ($responsive == true) {
@include generateResponsive() {
.d-b\@#{$breakpointAlias} { display: block !important; }
.d-ib\@#{$breakpointAlias} { display: inline-block !important; }
.d-i\@#{$breakpointAlias} { display: inline !important; }
.d-tb\@#{$breakpointAlias} { display: table !important; }
.d-tbc\@#{$breakpointAlias} { display: table-cell !important; }
.d-n\@#{$breakpointAlias} { display: none !important; }
}
}
// ---------------------------------------------------------
// @Overflow
// ---------------------------------------------------------
.ov-h { overflow: hidden !important; }
.ov-a { overflow: auto !important; }
.ov-s { overflow: scroll !important; }
.ovY-h { overflow-y: hidden !important; }
.ovX-h { overflow-x: hidden !important; }
.ovY-a { overflow-y: auto !important; }
.ovX-a { overflow-x: auto !important; }
.ovY-s { overflow-y: scroll !important; }
.ovX-s { overflow-x: scroll !important; }
@if ($responsive == true) {
@include generateResponsive() {
.ov-h\@#{$breakpointAlias} { overflow: hidden !important; }
.ov-a\@#{$breakpointAlias} { overflow: auto !important; }
.ov-s\@#{$breakpointAlias} { overflow: scroll !important; }
.ovY-h\@#{$breakpointAlias} { overflow-y: hidden !important; }
.ovX-h\@#{$breakpointAlias} { overflow-x: hidden !important; }
.ovY-a\@#{$breakpointAlias} { overflow-y: auto !important; }
.ovX-a\@#{$breakpointAlias} { overflow-x: auto !important; }
.ovY-s\@#{$breakpointAlias} { overflow-y: scroll !important; }
.ovX-s\@#{$breakpointAlias} { overflow-x: scroll !important; }
}
}
// ---------------------------------------------------------
// @Float
// ---------------------------------------------------------
.fl-l { float: left !important; }
.fl-r { float: right !important; }
.fl-n { float: none !important; }
@if ($responsive == true) {
@include generateResponsive() {
.fl-l\@#{$breakpointAlias} { float: left !important; }
.fl-r\@#{$breakpointAlias} { float: right !important; }
.fl-n\@#{$breakpointAlias} { float: none !important; }
}
}
// ---------------------------------------------------------
// @Vertical Align
// ---------------------------------------------------------
.va-t { vertical-align: top !important; }
.va-m { vertical-align: middle !important; }
.va-b { vertical-align: bottom !important; }
@if ($responsive == true) {
@include generateResponsive() {
.va-t\@#{$breakpointAlias} { vertical-align: top !important; }
.va-m\@#{$breakpointAlias} { vertical-align: middle !important; }
.va-b\@#{$breakpointAlias} { vertical-align: bottom !important; }
}
}
// ---------------------------------------------------------
// @Position
// ---------------------------------------------------------
.pos-s { position: static !important; }
.pos-st { position: sticky !important; }
.pos-r { position: relative !important; }
.pos-a { position: absolute !important; }
.pos-f { position: fixed !important; }
@if ($responsive == true) {
@include generateResponsive() {
.pos-s\@#{$breakpointAlias} { position: static !important; }
.pos-st\@#{$breakpointAlias} { position: sticky !important; }
.pos-r\@#{$breakpointAlias} { position: relative !important; }
.pos-a\@#{$breakpointAlias} { position: absolute !important; }
.pos-f\@#{$breakpointAlias} { position: fixed !important; }
}
}
// ---------------------------------------------------------
// @Z-Index
// ---------------------------------------------------------
@for $i from 0 through 9 {
.z-#{$i} { z-index: ($i * 1000) !important; }
@if ($responsive == true) {
@include generateResponsive() {
.z-#{$i}\@#{$breakpointAlias} { z-index: ($i * 1000) !important; }
}
}
}
================================================
FILE: src/assets/styles/spec/utils/layout/helpers/lists.scss
================================================
// ---------------------------------------------------------
// @TOC
// ---------------------------------------------------------
// + @List Style Position
// + @List Style Type
// ---------------------------------------------------------
// @List Style Position
// ---------------------------------------------------------
.lisp-i { list-style-position: inside; }
.lisp-o { list-style-position: outside; }
// ---------------------------------------------------------
// @List Style Type
// ---------------------------------------------------------
.lis-n { list-style: none; }
.list-c { list-style-type: circle; }
.list-s { list-style-type: square; }
.list-u { list-style-type: upper-roman; }
.list-l { list-style-type: lower-alpha; }
================================================
FILE: src/assets/styles/spec/utils/layout/helpers/margin.scss
================================================
@use '../mixins/generateResponsive' as *;
// ---------------------------------------------------------
// @TOC
// ---------------------------------------------------------
// + @Variables
// + @Margin (0 > 4 Step 1)
// + @Margin (5 > 35 Step 5)
// + @Margin (40 > 160 Step 10)
// + @Margin Auto
// ---------------------------------------------------------
// @Variables
// ---------------------------------------------------------
$responsive: true;
// ---------------------------------------------------------
// @Padding (0 > 4 Step 1)
// ---------------------------------------------------------
@for $i from 0 through 4 {
.m-#{$i} { margin: #{$i}px !important; }
.mT-#{$i} { margin-top: #{$i}px !important; }
.mR-#{$i} { margin-right: #{$i}px !important; }
.mB-#{$i} { margin-bottom: #{$i}px !important; }
.mL-#{$i} { margin-left: #{$i}px !important; }
.mY-#{$i} {
margin-top: #{$i}px !important;
margin-bottom: #{$i}px !important;
}
.mX-#{$i} {
margin-left: #{$i}px !important;
margin-right: #{$i}px !important;
}
.m-nv-#{$i} { margin: -#{$i}px !important; }
.mT-nv-#{$i} { margin-top: -#{$i}px !important; }
.mR-nv-#{$i} { margin-right: -#{$i}px !important; }
.mB-nv-#{$i} { margin-bottom:- #{$i}px !important; }
.mL-nv-#{$i} { margin-left: -#{$i}px !important; }
.mY-nv-#{$i} {
margin-top: -#{$i}px !important;
margin-bottom: -#{$i}px !important;
}
.mX-nv-#{$i} {
margin-left: -#{$i}px !important;
margin-right: -#{$i}px !important;
}
@if ($responsive == true) {
@include generateResponsive() {
.m-#{$i}\@#{$breakpointAlias} { margin: #{$i}px !important; }
.mT-#{$i}\@#{$breakpointAlias} { margin-top: #{$i}px !important; }
.mR-#{$i}\@#{$breakpointAlias} { margin-right: #{$i}px !important; }
.mB-#{$i}\@#{$breakpointAlias} { margin-bottom: #{$i}px !important; }
.mL-#{$i}\@#{$breakpointAlias} { margin-left: #{$i}px !important; }
.mY-#{$i}\@#{$breakpointAlias} {
margin-top: #{$i}px !important;
margin-bottom: #{$i}px !important;
}
.mX-#{$i}\@#{$breakpointAlias} {
margin-left: #{$i}px !important;
margin-right: #{$i}px !important;
}
.m-nv-#{$i}\@#{$breakpointAlias} { margin: -#{$i}px !important; }
.mT-nv-#{$i}\@#{$breakpointAlias} { margin-top: -#{$i}px !important; }
.mR-nv-#{$i}\@#{$breakpointAlias} { margin-right: -#{$i}px !important; }
.mB-nv-#{$i}\@#{$breakpointAlias} { margin-bottom: -#{$i}px !important; }
.mL-nv-#{$i}\@#{$breakpointAlias} { margin-left: -#{$i}px !important; }
.mY-nv-#{$i}\@#{$breakpointAlias} {
margin-top: -#{$i}px !important;
margin-bottom: -#{$i}px !important;
}
.mX-nv-#{$i}\@#{$breakpointAlias} {
margin-left: -#{$i}px !important;
margin-right: -#{$i}px !important;
}
}
}
}
// ---------------------------------------------------------
// @Padding (5 > 35 Step 5)
// ---------------------------------------------------------
@for $i from 5 through 35 {
@if $i % 5 == 0 {
.m-#{$i} { margin: #{$i}px !important; }
.mT-#{$i} { margin-top: #{$i}px !important; }
.mR-#{$i} { margin-right: #{$i}px !important; }
.mB-#{$i} { margin-bottom: #{$i}px !important; }
.mL-#{$i} { margin-left: #{$i}px !important; }
.mY-#{$i} {
margin-top: #{$i}px !important;
margin-bottom: #{$i}px !important;
}
.mX-#{$i} {
margin-left: #{$i}px !important;
margin-right: #{$i}px !important;
}
.m-nv-#{$i} { margin: -#{$i}px !important; }
.mT-nv-#{$i} { margin-top: -#{$i}px !important; }
.mR-nv-#{$i} { margin-right: -#{$i}px !important; }
.mB-nv-#{$i} { margin-bottom: -#{$i}px !important; }
.mL-nv-#{$i} { margin-left: -#{$i}px !important; }
.mY-nv-#{$i} {
margin-top: -#{$i}px !important;
margin-bottom: -#{$i}px !important;
}
.mX-nv-#{$i} {
margin-left: -#{$i}px !important;
margin-right: -#{$i}px !important;
}
@if ($responsive == true) {
@include generateResponsive() {
.m-#{$i}\@#{$breakpointAlias} { margin: #{$i}px !important; }
.mT-#{$i}\@#{$breakpointAlias} { margin-top: #{$i}px !important; }
.mR-#{$i}\@#{$breakpointAlias} { margin-right: #{$i}px !important; }
.mB-#{$i}\@#{$breakpointAlias} { margin-bottom: #{$i}px !important; }
.mL-#{$i}\@#{$breakpointAlias} { margin-left: #{$i}px !important; }
.mY-#{$i}\@#{$breakpointAlias} {
margin-top: #{$i}px !important;
margin-bottom: #{$i}px !important;
}
.mX-#{$i}\@#{$breakpointAlias} {
margin-left: #{$i}px !important;
margin-right: #{$i}px !important;
}
.m-nv-#{$i}\@#{$breakpointAlias} { margin: -#{$i}px !important; }
.mT-nv-#{$i}\@#{$breakpointAlias} { margin-top: -#{$i}px !important; }
.mR-nv-#{$i}\@#{$breakpointAlias} { margin-right: -#{$i}px !important; }
.mB-nv-#{$i}\@#{$breakpointAlias} { margin-bottom: -#{$i}px !important; }
.mL-nv-#{$i}\@#{$breakpointAlias} { margin-left: -#{$i}px !important; }
.mY-nv-#{$i}\@#{$breakpointAlias} {
margin-top: -#{$i}px !important;
margin-bottom: -#{$i}px !important;
}
.mX-nv-#{$i}\@#{$breakpointAlias} {
margin-left: -#{$i}px !important;
margin-right: -#{$i}px !important;
}
}
}
}
}
// ---------------------------------------------------------
// @Padding (40 > 160 Step 10)
// ---------------------------------------------------------
@for $i from 40 through 160 {
@if $i % 10 == 0 {
.m-#{$i} { margin: #{$i}px !important; }
.mT-#{$i} { margin-top: #{$i}px !important; }
.mR-#{$i} { margin-right: #{$i}px !important; }
.mB-#{$i} { margin-bottom: #{$i}px !important; }
.mL-#{$i} { margin-left: #{$i}px !important; }
.mY-#{$i} {
margin-top: #{$i}px !important;
margin-bottom: #{$i}px !important;
}
.mX-#{$i} {
margin-left: #{$i}px !important;
margin-right: #{$i}px !important;
}
.m-nv-#{$i} { margin: -#{$i}px !important; }
.mT-nv-#{$i} { margin-top: -#{$i}px !important; }
.mR-nv-#{$i} { margin-right: -#{$i}px !important; }
.mB-nv-#{$i} { margin-bottom: -#{$i}px !important; }
.mL-nv-#{$i} { margin-left: -#{$i}px !important; }
.mY-nv-#{$i} {
margin-top: -#{$i}px !important;
margin-bottom: -#{$i}px !important;
}
.mX-nv-#{$i} {
margin-left: -#{$i}px !important;
margin-right: -#{$i}px !important;
}
@if ($responsive == true) {
@include generateResponsive() {
.m-#{$i}\@#{$breakpointAlias} { margin: #{$i}px !important; }
.mT-#{$i}\@#{$breakpointAlias} { margin-top: #{$i}px !important; }
.mR-#{$i}\@#{$breakpointAlias} { margin-right: #{$i}px !important; }
.mB-#{$i}\@#{$breakpointAlias} { margin-bottom: #{$i}px !important; }
.mL-#{$i}\@#{$breakpointAlias} { margin-left: #{$i}px !important; }
.mY-#{$i}\@#{$breakpointAlias} {
margin-top: #{$i}px !important;
margin-bottom: #{$i}px !important;
}
.mX-#{$i}\@#{$breakpointAlias} {
margin-left: #{$i}px !important;
margin-right: #{$i}px !important;
}
.m-nv-#{$i}\@#{$breakpointAlias} { margin: -#{$i}px !important; }
.mT-nv-#{$i}\@#{$breakpointAlias} { margin-top: -#{$i}px !important; }
.mR-nv-#{$i}\@#{$breakpointAlias} { margin-right: -#{$i}px !important; }
.mB-nv-#{$i}\@#{$breakpointAlias} { margin-bottom: -#{$i}px !important; }
.mL-nv-#{$i}\@#{$breakpointAlias} { margin-left: -#{$i}px !important; }
.mY-nv-#{$i}\@#{$breakpointAlias} {
margin-top: -#{$i}px !important;
margin-bottom: -#{$i}px !important;
}
.mX-nv-#{$i}\@#{$breakpointAlias} {
margin-left: -#{$i}px !important;
margin-right: -#{$i}px !important;
}
}
}
}
}
// ---------------------------------------------------------
// @Padding Auto
// ---------------------------------------------------------
.m-a { margin: auto !important; }
.mX-a { margin-left: auto !important; margin-right: auto !important; }
.mT-a { margin-top: auto !important; }
.mR-a { margin-right: auto !important; }
.mB-a { margin-bottom: auto !important; }
.mL-a { margin-left: auto !important; }
@if ($responsive == true) {
@include generateResponsive() {
.m-a\@#{$breakpointAlias} { margin: auto !important; }
.mX-a\@#{$breakpointAlias} { margin-left: auto !important; margin-right: auto !important; }
.mT-a\@#{$breakpointAlias} { margin-top: auto !important; }
.mR-a\@#{$breakpointAlias} { margin-right: auto !important; }
.mB-a\@#{$breakpointAlias} { margin-bottom: auto !important; }
.mL-a\@#{$breakpointAlias} { margin-left: auto !important; }
}
}
================================================
FILE: src/assets/styles/spec/utils/layout/helpers/objects.scss
================================================
// ---------------------------------------------------------
// @TOC
// ---------------------------------------------------------
// + @Background Size
// + @Background Position
// + @Background Repeat
// + @Object Fit
// + @Resize
// + @Opacity
// + @Cursor
// + @Visibility
// ---------------------------------------------------------
// @Background Size
// ---------------------------------------------------------
.bgsz-cv { background-size: cover; }
.bgsz-ct { background-size: contain; }
.bgsz-full { background-size: 100% 100%; }
// ---------------------------------------------------------
// @Background Position
// ---------------------------------------------------------
.bgpX-c { background-position-x: center; }
.bgpX-t { background-position-x: top; }
.bgpX-r { background-position-x: right; }
.bgpX-l { background-position-x: left; }
.bgpX-b { background-position-x: bottom; }
.bgpY-c { background-position-y: center; }
.bgpY-t { background-position-y: top; }
.bgpY-r { background-position-y: right; }
.bgpY-l { background-position-y: left; }
.bgpY-b { background-position-y: bottom; }
// ---------------------------------------------------------
// @Background Repeat
// ---------------------------------------------------------
.bgr-n { background-repeat: no-repeat; }
.bgr-y { background-repeat: repeat-y; }
.bgr-x { background-repeat: repeat-x; }
// ---------------------------------------------------------
// @Object Fit
// ---------------------------------------------------------
.of-ct { object-fit: contain; }
.of-cv { object-fit: cover; }
.of-f { object-fit: fill; }
.of-n { object-fit: none; }
.of-sd { object-fit: scale-down; }
// ---------------------------------------------------------
// @Resize
// ---------------------------------------------------------
.rsz-v { resize: vertical; }
.rsz-h { resize: horizontal; }
// ---------------------------------------------------------
// @Opacity
// ---------------------------------------------------------
.op-0 { opacity: 0; }
.op-10p { opacity: 0.1; }
.op-20p { opacity: 0.2; }
.op-30p { opacity: 0.3; }
.op-40p { opacity: 0.4; }
.op-50p { opacity: 0.5; }
.op-60p { opacity: 0.6; }
.op-70p { opacity: 0.7; }
.op-80p { opacity: 0.8; }
.op-90p { opacity: 0.9; }
.op-100p { opacity: 1; }
// ---------------------------------------------------------
// @Cursor
// ---------------------------------------------------------
.cur-na { cursor: not-allowed; }
.cur-p { cursor: pointer; }
.cur-a { cursor: auto; }
// ---------------------------------------------------------
// @Visibility
// ---------------------------------------------------------
.vis-v { visibility: visible; }
.vis-h { visibility: hidden; }
================================================
FILE: src/assets/styles/spec/utils/layout/helpers/padding.scss
================================================
@use '../mixins/generateResponsive' as *;
// ---------------------------------------------------------
// @TOC
// ---------------------------------------------------------
// + @Variables
// + @Padding (0 > 4 Step 1)
// + @Padding (5 > 35 Step 5)
// + @Padding (40 > 160 Step 10)
// + @Padding Auto
// ---------------------------------------------------------
// @Variables
// ---------------------------------------------------------
$responsive: true;
// ---------------------------------------------------------
// @Padding (0 > 4 Step 1)
// ---------------------------------------------------------
@for $i from 0 through 4 {
.p-#{$i} { padding: #{$i}px !important; }
.pT-#{$i} { padding-top: #{$i}px !important; }
.pR-#{$i} { padding-right: #{$i}px !important; }
.pB-#{$i} { padding-bottom: #{$i}px !important; }
.pL-#{$i} { padding-left: #{$i}px !important; }
.pY-#{$i} {
padding-top: #{$i}px !important;
padding-bottom: #{$i}px !important;
}
.pX-#{$i} {
padding-left: #{$i}px !important;
padding-right: #{$i}px !important;
}
@if ($responsive == true) {
@include generateResponsive() {
.p-#{$i}\@#{$breakpointAlias} { padding: #{$i}px !important; }
.pT-#{$i}\@#{$breakpointAlias} { padding-top: #{$i}px !important; }
.pR-#{$i}\@#{$breakpointAlias} { padding-right: #{$i}px !important; }
.pB-#{$i}\@#{$breakpointAlias} { padding-bottom: #{$i}px !important; }
.pL-#{$i}\@#{$breakpointAlias} { padding-left: #{$i}px !important; }
.pY-#{$i}\@#{$breakpointAlias} {
padding-top: #{$i}px !important;
padding-bottom: #{$i}px !important;
}
.pX-#{$i}\@#{$breakpointAlias} {
padding-left: #{$i}px !important;
padding-right: #{$i}px !important;
}
}
}
}
// ---------------------------------------------------------
// @Padding (5 > 35 Step 5)
// ---------------------------------------------------------
@for $i from 5 through 35 {
@if $i % 5 == 0 {
.p-#{$i} { padding: #{$i}px !important; }
.pT-#{$i} { padding-top: #{$i}px !important; }
.pR-#{$i} { padding-right: #{$i}px !important; }
.pB-#{$i} { padding-bottom: #{$i}px !important; }
.pL-#{$i} { padding-left: #{$i}px !important; }
.pY-#{$i} {
padding-top: #{$i}px !important;
padding-bottom: #{$i}px !important;
}
.pX-#{$i} {
padding-left: #{$i}px !important;
padding-right: #{$i}px !important;
}
@if ($responsive == true) {
@include generateResponsive() {
.p-#{$i}\@#{$breakpointAlias} { padding: #{$i}px !important; }
.pT-#{$i}\@#{$breakpointAlias} { padding-top: #{$i}px !important; }
.pR-#{$i}\@#{$breakpointAlias} { padding-right: #{$i}px !important; }
.pB-#{$i}\@#{$breakpointAlias} { padding-bottom: #{$i}px !important; }
.pL-#{$i}\@#{$breakpointAlias} { padding-left: #{$i}px !important; }
.pY-#{$i}\@#{$breakpointAlias} {
padding-top: #{$i}px !important;
padding-bottom: #{$i}px !important;
}
.pX-#{$i}\@#{$breakpointAlias} {
padding-left: #{$i}px !important;
padding-right: #{$i}px !important;
}
}
}
}
}
// ---------------------------------------------------------
// @Padding (40 > 160 Step 10)
// ---------------------------------------------------------
@for $i from 40 through 160 {
@if $i % 10 == 0 {
.p-#{$i} { padding: #{$i}px !important; }
.pT-#{$i} { padding-top: #{$i}px !important; }
.pR-#{$i} { padding-right: #{$i}px !important; }
.pB-#{$i} { padding-bottom: #{$i}px !important; }
.pL-#{$i} { padding-left: #{$i}px !important; }
.pY-#{$i} {
padding-top: #{$i}px !important;
padding-bottom: #{$i}px !important;
}
.pX-#{$i} {
padding-left: #{$i}px !important;
padding-right: #{$i}px !important;
}
@if ($responsive == true) {
@include generateResponsive() {
.p-#{$i}\@#{$breakpointAlias} { padding: #{$i}px !important; }
.pT-#{$i}\@#{$breakpointAlias} { padding-top: #{$i}px !important; }
.pR-#{$i}\@#{$breakpointAlias} { padding-right: #{$i}px !important; }
.pB-#{$i}\@#{$breakpointAlias} { padding-bottom: #{$i}px !important; }
.pL-#{$i}\@#{$breakpointAlias} { padding-left: #{$i}px !important; }
.pY-#{$i}\@#{$breakpointAlias} {
padding-top: #{$i}px !important;
padding-bottom: #{$i}px !important;
}
.pX-#{$i}\@#{$breakpointAlias} {
padding-left: #{$i}px !important;
padding-right: #{$i}px !important;
}
}
}
}
}
================================================
FILE: src/assets/styles/spec/utils/layout/helpers/positions.scss
================================================
@use '../mixins/generateResponsive' as *;
// ---------------------------------------------------------
// @TOC
// ---------------------------------------------------------
// + @Variables
// + @Position (0 > 4 Step 1)
// + @Position (5 > 35 Step 5)
// + @Position (40 > 160 Step 10)
// ---------------------------------------------------------
// @Variables
// ---------------------------------------------------------
$responsive: true;
// ---------------------------------------------------------
// @Position (0 > 4 Step 1)
// ---------------------------------------------------------
@for $i from 0 through 4 {
.t-#{$i} { top: #{$i}px; }
.r-#{$i} { right: #{$i}px; }
.b-#{$i} { bottom: #{$i}px; }
.l-#{$i} { left: #{$i}px; }
@if ($responsive == true) {
@include generateResponsive() {
.t-#{$i}\@#{$breakpointAlias} { top: #{$i}px; }
.r-#{$i}\@#{$breakpointAlias} { right: #{$i}px; }
.b-#{$i}\@#{$breakpointAlias} { bottom: #{$i}px; }
.l-#{$i}\@#{$breakpointAlias} { left: #{$i}px; }
}
}
}
// ---------------------------------------------------------
// @Position (5 > 35 Step 5)
// ---------------------------------------------------------
@for $i from 5 through 35 {
@if $i % 5 == 0 {
.t-#{$i} { top: #{$i}px; }
.r-#{$i} { right: #{$i}px; }
.b-#{$i} { bottom: #{$i}px; }
.l-#{$i} { left: #{$i}px; }
@if ($responsive == true) {
@include generateResponsive() {
.t-#{$i}\@#{$breakpointAlias} { top: #{$i}px; }
.r-#{$i}\@#{$breakpointAlias} { right: #{$i}px; }
.b-#{$i}\@#{$breakpointAlias} { bottom: #{$i}px; }
.l-#{$i}\@#{$breakpointAlias} { left: #{$i}px; }
}
}
}
}
// ---------------------------------------------------------
// @Position (40 > 160 Step 10)
// ---------------------------------------------------------
@for $i from 40 through 160 {
@if $i % 10 == 0 {
.t-#{$i} { top: #{$i}px; }
.r-#{$i} { right: #{$i}px; }
.b-#{$i} { bottom: #{$i}px; }
.l-#{$i} { left: #{$i}px; }
@if ($responsive == true) {
@include generateResponsive() {
.t-#{$i}\@#{$breakpointAlias} { top: #{$i}px; }
.r-#{$i}\@#{$breakpointAlias} { right: #{$i}px; }
.b-#{$i}\@#{$breakpointAlias} { bottom: #{$i}px; }
.l-#{$i}\@#{$breakpointAlias} { left: #{$i}px; }
}
}
}
}
// ---------------------------------------------------------
// @Position (50%)
// ---------------------------------------------------------
.tl-50p {
top: 50%;
left: 50%;
}
.tr-50p {
top: 50%;
right: 50%;
}
.t-50p { top: 50%; }
.r-50p { right: 50%; }
.b-50p { bottom: 50%; }
.l-50p { left: 50%; }
@if ($responsive == true) {
@include generateResponsive() {
.tl-50p\@#{$breakpointAlias} {
top: 50%;
left: 50%;
}
.tr-50p\@#{$breakpointAlias} {
top: 50%;
right: 50%;
}
.t-50p\@#{$breakpointAlias} { top: 50%; }
.r-50p\@#{$breakpointAlias} { right: 50%; }
.b-50p\@#{$breakpointAlias} { bottom: 50%; }
.l-50p\@#{$breakpointAlias} { left: 50%; }
}
}
================================================
FILE: src/assets/styles/spec/utils/layout/helpers/pseudo.scss
================================================
// ---------------------------------------------------------
// @Pseudo Elements
// ---------------------------------------------------------
.no-after::after { display: none !important; }
.no-before::before { display: none !important; }
================================================
FILE: src/assets/styles/spec/utils/layout/helpers/sizes.scss
================================================
@use '../mixins/generateResponsive' as *;
// ---------------------------------------------------------
// @TOC
// ---------------------------------------------------------
// + @Variables
// + @Fixed Width
// + @Relative Width
// + @Fixed Height
// + @Max Size
// ---------------------------------------------------------
// @Variables
// ---------------------------------------------------------
$responsive: true;
// ---------------------------------------------------------
// @Fixed Width
// ---------------------------------------------------------
.w-1\/4r, .sz-1\/4r { width: 0.25rem; }
.w-1\/2r, .sz-1\/2r { width: 0.5rem; }
.w-3\/4r, .sz-3\/4r { width: 0.75rem; }
.w-1r, .sz-1r { width: 1rem; }
.w-3\/2r, .sz-3\/2r { width: 1.5rem; }
.w-2r, .sz-2r { width: 2rem; }
.w-5\/2r, .sz-5\/2r { width: 2.5rem; }
.w-3r, .sz-3r { width: 3rem; }
.w-7\/2r, .sz-7\/2r { width: 3.5rem; }
.w-4r, .sz-4r { width: 4rem; }
.w-9\/2r, .sz-9\/2r { width: 4.5rem; }
.w-5r, .sz-5r { width: 5rem; }
.w-11\/2r, .sz-11\/2r { width: 5.5rem; }
.w-6r, .sz-6r { width: 6rem; }
@if ($responsive == true) {
@include generateResponsive() {
.w-1\/4r\@#{$breakpointAlias}, .sz-1\/4r\@#{$breakpointAlias} { width: 0.25rem; }
.w-1\/2r\@#{$breakpointAlias}, .sz-1\/2r\@#{$breakpointAlias} { width: 0.5rem; }
.w-3\/4r\@#{$breakpointAlias}, .sz-3\/4r\@#{$breakpointAlias} { width: 0.75rem; }
.w-1r\@#{$breakpointAlias}, .sz-1r\@#{$breakpointAlias} { width: 1rem; }
.w-3\/2r\@#{$breakpointAlias}, .sz-3\/2r\@#{$breakpointAlias} { width: 1.5rem; }
.w-2r\@#{$breakpointAlias}, .sz-2r\@#{$breakpointAlias} { width: 2rem; }
.w-5\/2r\@#{$breakpointAlias}, .sz-5\/2r\@#{$breakpointAlias} { width: 2.5rem; }
.w-3r\@#{$breakpointAlias}, .sz-3r\@#{$breakpointAlias} { width: 3rem; }
.w-7\/2r\@#{$breakpointAlias}, .sz-7\/2r\@#{$breakpointAlias} { width: 3.5rem; }
.w-4r\@#{$breakpointAlias}, .sz-4r\@#{$breakpointAlias} { width: 4rem; }
.w-9\/2r\@#{$breakpointAlias}, .sz-9\/2r\@#{$breakpointAlias} { width: 4.5rem; }
.w-5r\@#{$breakpointAlias}, .sz-5r\@#{$breakpointAlias} { width: 5rem; }
.w-11\/2r\@#{$breakpointAlias}, .sz-11\/2r\@#{$breakpointAlias} { width: 5.5rem; }
.w-6r\@#{$breakpointAlias}, .sz-6r\@#{$breakpointAlias} { width: 6rem; }
}
}
// ---------------------------------------------------------
// @Relative Width
// ---------------------------------------------------------
.w-0 { width: 0px; }
.w-10p { width: 10%; }
.w-20p { width: 20%; }
.w-30p { width: 30%; }
.w-40p { width: 40%; }
.w-50p { width: 50%; }
.w-60p { width: 60%; }
.w-70p { width: 70%; }
.w-80p { width: 80%; }
.w-90p { width: 90%; }
.w-100p { width: 100%; }
.w-1px { width: 1px; }
.w-a { width: auto; }
@if ($responsive == true) {
@include generateResponsive() {
.w-0\@#{$breakpointAlias} { width: 0px; }
.w-10p\@#{$breakpointAlias} { width: 10%; }
.w-20p\@#{$breakpointAlias} { width: 20%; }
.w-30p\@#{$breakpointAlias} { width: 30%; }
.w-40p\@#{$breakpointAlias} { width: 40%; }
.w-50p\@#{$breakpointAlias} { width: 50%; }
.w-60p\@#{$breakpointAlias} { width: 60%; }
.w-70p\@#{$breakpointAlias} { width: 70%; }
.w-80p\@#{$breakpointAlias} { width: 80%; }
.w-90p\@#{$breakpointAlias} { width: 90%; }
.w-100p\@#{$breakpointAlias} { width: 100%; }
.w-1px\@#{$breakpointAlias} { width: 1px; }
.w-a\@#{$breakpointAlias} { width: auto; }
}
}
// ---------------------------------------------------------
// @Fixed Height
// ---------------------------------------------------------
.h-1\/4r, .sz-1\/4r { height: 0.25rem; }
.h-1\/2r, .sz-1\/2r { height: 0.5rem; }
.h-3\/4r, .sz-3\/4r { height: 0.75rem; }
.h-1r, .sz-1r { height: 1rem; }
.h-3\/2r, .sz-3\/2r { height: 1.5rem; }
.h-2r, .sz-2r { height: 2rem; }
.h-5\/2r, .sz-5\/2r { height: 2.5rem; }
.h-3r, .sz-3r { height: 3rem; }
.h-7\/2r, .sz-7\/2r { height: 3.5rem; }
.h-4r, .sz-4r { height: 4rem; }
.h-9\/2r, .sz-9\/2r { height: 4.5rem; }
.h-5r, .sz-5r { height: 5rem; }
.h-11\/2r, .sz-11\/2r { height: 5.5rem; }
.h-6r, .sz-6r { height: 6rem; }
@if ($responsive == true) {
@include generateResponsive() {
.h-1\/4r\@#{$breakpointAlias}, .sz-1\/4r\@#{$breakpointAlias} { height: 0.25rem; }
.h-1\/2r\@#{$breakpointAlias}, .sz-1\/2r\@#{$breakpointAlias} { height: 0.5rem; }
.h-3\/4r\@#{$breakpointAlias}, .sz-3\/4r\@#{$breakpointAlias} { height: 0.75rem; }
.h-1r\@#{$breakpointAlias}, .sz-1r\@#{$breakpointAlias} { height: 1rem; }
.h-3\/2r\@#{$breakpointAlias}, .sz-3\/2r\@#{$breakpointAlias} { height: 1.5rem; }
.h-2r\@#{$breakpointAlias}, .sz-2r\@#{$breakpointAlias} { height: 2rem; }
.h-5\/2r\@#{$breakpointAlias}, .sz-5\/2r\@#{$breakpointAlias} { height: 2.5rem; }
.h-3r\@#{$breakpointAlias}, .sz-3r\@#{$breakpointAlias} { height: 3rem; }
.h-7\/2r\@#{$breakpointAlias}, .sz-7\/2r\@#{$breakpointAlias} { height: 3.5rem; }
.h-4r\@#{$breakpointAlias}, .sz-4r\@#{$breakpointAlias} { height: 4rem; }
.h-9\/2r\@#{$breakpointAlias}, .sz-9\/2r\@#{$breakpointAlias} { height: 4.5rem; }
.h-5r\@#{$breakpointAlias}, .sz-5r\@#{$breakpointAlias} { height: 5rem; }
.h-11\/2r\@#{$breakpointAlias}, .sz-11\/2r\@#{$breakpointAlias} { height: 5.5rem; }
.h-6r\@#{$breakpointAlias}, .sz-6r\@#{$breakpointAlias} { height: 6rem; }
}
}
.h-0 { height: 0; }
.h-auto { height: auto; }
.h-100p { height: 100%; }
.h-100vh { height: 100vh; }
@if ($responsive == true) {
@include generateResponsive() {
.h-0\@#{$breakpointAlias} { height: 0; }
.h-auto\@#{$breakpointAlias} { height: auto; }
.h-100p\@#{$breakpointAlias} { height: 100%; }
.h-100vh\@#{$breakpointAlias} { height: 100vh; }
}
}
// ---------------------------------------------------------
// @Max Size
// ---------------------------------------------------------
.mw-100p { max-width: 100%; }
.mh-100p { max-height: 100%; }
@if ($responsive == true) {
@include generateResponsive() {
.mw-100p\@#{$breakpointAlias} { max-width: 100%; }
.mh-100p\@#{$breakpointAlias} { max-height: 100%; }
}
}
================================================
FILE: src/assets/styles/spec/utils/layout/helpers/typography.scss
================================================
@use '../mixins/generateResponsive' as *;
// ---------------------------------------------------------
// @TOC
// ---------------------------------------------------------
// + @Variables
// + @Text Align
// + @Text Transform
// + @Font Style
// + @Text Decoration
// + @White Space
// + @Word Break
// + @Word Wrap
// + @Text Overflow
// + @Font Size
// + @Font Weight
// + @Line Height
// ---------------------------------------------------------
// @Variables
// ---------------------------------------------------------
$responsive: true;
// ---------------------------------------------------------
// @Text Align
// ---------------------------------------------------------
.ta-c { text-align: center !important; }
.ta-l { text-align: left !important; }
.ta-r { text-align: right !important; }
@if ($responsive == true) {
@include generateResponsive() {
.ta-c\@#{$breakpointAlias} { text-align: center !important; }
.ta-l\@#{$breakpointAlias} { text-align: left !important; }
.ta-r\@#{$breakpointAlias} { text-align: right !important; }
}
}
// ---------------------------------------------------------
// @Text Transform
// ---------------------------------------------------------
.tt-n { text-transform: none !important; }
.tt-u { text-transform: uppercase !important; }
.tt-l { text-transform: lowercase !important; }
.tt-c { text-transform: capitalize !important; }
// ---------------------------------------------------------
// @Font Style
// ---------------------------------------------------------
.fs-i { font-style: italic !important; }
.fs-o { font-style: oblique !important; }
// ---------------------------------------------------------
// @Text Decoration
// ---------------------------------------------------------
.td-n { text-decoration: none !important; }
.td-o { text-decoration: overline !important; }
.td-lt { text-decoration: line-through !important; }
.td-u { text-decoration: underline !important; }
// ---------------------------------------------------------
// @White Space
// ---------------------------------------------------------
.whs-nw { white-space: nowrap !important; }
.whs-p { white-space: pre !important; }
.whs-n { white-space: normal !important; }
// ---------------------------------------------------------
// @Word Break
// ---------------------------------------------------------
.wob-n { word-break: normal !important; }
.wob-ba { word-break: break-all !important; }
.wob-k { word-break: keep-all !important; }
// ---------------------------------------------------------
// @Word Wrap
// ---------------------------------------------------------
.wow-bw { word-wrap: break-word !important; }
.wow-n { word-wrap: normal !important; }
// ---------------------------------------------------------
// @Text Overflow
// ---------------------------------------------------------
.tov-e { text-overflow: ellipsis !important; }
// ---------------------------------------------------------
// @Font Size
// ---------------------------------------------------------
.fsz-xs { font-size: 0.75rem !important; }
.fsz-sm { font-size: 0.87rem !important; }
.fsz-def { font-size: 1rem !important; }
.fsz-md { font-size: 1.15rem !important; }
.fsz-lg { font-size: 1.4rem !important; }
.fsz-xl { font-size: 1.7rem !important; }
// ---------------------------------------------------------
// @Font Weight
// ---------------------------------------------------------
.fw-100 { font-weight: 100 !important; }
.fw-200 { font-weight: 200 !important; }
.fw-300 { font-weight: 300 !important; }
.fw-400 { font-weight: 400 !important; }
.fw-500 { font-weight: 500 !important; }
.fw-600 { font-weight: 600 !important; }
.fw-700 { font-weight: 700 !important; }
.fw-800 { font-weight: 800 !important; }
.fw-900 { font-weight: 900 !important; }
// ---------------------------------------------------------
// @Line Height
// ---------------------------------------------------------
.lh-0 { line-height: 0 !important; }
.lh-1 { line-height: 1 !important; }
.lh-3\/2 { line-height: 1.5 !important; }
================================================
FILE: src/assets/styles/spec/utils/layout/index.scss
================================================
@use 'mixins/index' as *;
@use 'utils/index' as *;
@use 'helpers/index' as *;
================================================
FILE: src/assets/styles/spec/utils/layout/mixins/generateResponsive.scss
================================================
@use '../../../settings/breakpoints' as *;
@use 'mediaQueryCondition' as *;
// Initialize global variable to avoid deprecation warning
$breakpointAlias: null;
// ---------------------------------------------------------
// @Responsive Suffix Generator
// ---------------------------------------------------------
// Mixin used to generate responsive suffixes for classes (i.e. m-10@sm+).
@mixin generateResponsive() {
@each $breakpoint in $breakpoints {
$breakpointAlias : nth($breakpoint, 1) !global;
$breakpointCondition : nth($breakpoint, 2);
@include mediaQueryCondition($breakpointAlias) {
@content;
}
$breakpointAlias: null !global;
}
}
================================================
FILE: src/assets/styles/spec/utils/layout/mixins/index.scss
================================================
@use 'mediaQueryCondition' as *;
@use 'generateResponsive' as *;
================================================
FILE: src/assets/styles/spec/utils/layout/mixins/mediaQueryCondition.scss
================================================
@use '../../../settings/breakpoints' as *;
// ---------------------------------------------------------
// @Media Queries Generator
// ---------------------------------------------------------
// Mixin used to generate responsive versions of css rules.
@mixin mediaQueryCondition($mq) {
$breakpointFound: false;
@each $breakpoint in $breakpoints {
$alias: nth($breakpoint, 1);
$condition: nth($breakpoint, 2);
@if $mq == $alias and $condition {
$breakpointFound: true;
@media #{$condition} {
@content;
}
}
}
@if $breakpointFound == false {
@warn "Oops! Breakpoint ‘#{$mq}’ does not exist \:";
}
}
================================================
FILE: src/assets/styles/spec/utils/layout/utils/center.scss
================================================
@use '../mixins/generateResponsive' as *;
// ---------------------------------------------------------
// @TOC
// ---------------------------------------------------------
// + @Variables
// + @Centering
// ---------------------------------------------------------
// @Variables
// ---------------------------------------------------------
$responsive: true;
// ---------------------------------------------------------
// @Centering
// ---------------------------------------------------------
.centerY {
top: 50%;
transform: translateY(-50%);
}
.centerX {
left: 50%;
transform: translateX(-50%);
}
.centerXY {
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
@if ($responsive == true) {
@include generateResponsive() {
.centerY\@#{$breakpointAlias} {
top: 50%;
transform: translateY(-50%);
}
.centerX\@#{$breakpointAlias} {
left: 50%;
transform: translateX(-50%);
}
.centerXY\@#{$breakpointAlias} {
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
}
}
================================================
FILE: src/assets/styles/spec/utils/layout/utils/gap.scss
================================================
@use "sass:math";
@use '../mixins/generateResponsive' as *;
// ---------------------------------------------------------
// @TOC
// ---------------------------------------------------------
// + @Variables
// + @Base
// + @Gap (0 > 4 Step 1)
// + @Gap (5 > 35 Step 5)
// + @Gap (40 > 160 Step 10)
// ---------------------------------------------------------
// @Variables
// ---------------------------------------------------------
$responsive: true;
// ---------------------------------------------------------
// @Base
// ---------------------------------------------------------
[class*='gap'] {
width: auto !important;
overflow: hidden !important;
}
// ---------------------------------------------------------
// @Gap (0 > 4 Step 1)
// ---------------------------------------------------------
@for $i from 0 through 4 {
.gapX-#{$i} {
margin-left: #{math.div($i, -2)}px !important;
margin-right: #{math.div($i, -2)}px !important;
& > * {
padding-left: #{math.div($i, 2)}px !important;
padding-right: #{math.div($i, 2)}px !important;
}
}
.gapY-#{$i} {
margin-top: #{math.div($i, -2)}px !important;
margin-bottom: #{math.div($i, -2)}px !important;
& > * {
padding-top: #{math.div($i, 2)}px !important;
padding-bottom: #{math.div($i, 2)}px !important;
}
}
.gap-#{$i} {
margin: #{math.div($i, -2)}px !important;
& > * {
padding: #{math.div($i, 2)}px !important;
}
}
@if ($responsive == true) {
@include generateResponsive() {
.gapX-#{$i}\@#{$breakpointAlias} {
margin-left: #{math.div($i, -2)}px !important;
margin-right: #{math.div($i, -2)}px !important;
& > * {
padding-left: #{math.div($i, 2)}px !important;
padding-right: #{math.div($i, 2)}px !important;
}
}
.gapY-#{$i}\@#{$breakpointAlias} {
margin-top: #{math.div($i, -2)}px !important;
margin-bottom: #{math.div($i, -2)}px !important;
& > * {
padding-top: #{math.div($i, 2)}px !important;
padding-bottom: #{math.div($i, 2)}px !important;
}
}
.gap-#{$i}\@#{$breakpointAlias} {
margin: #{math.div($i, -2)}px !important;
& > * {
padding: #{math.div($i, 2)}px !important;
}
}
}
}
}
// ---------------------------------------------------------
// @Gap (5 > 35 Step 5)
// ---------------------------------------------------------
@for $i from 5 through 35 {
@if $i % 5 == 0 {
.gapX-#{$i} {
margin-left: #{math.div($i, -2)}px !important;
margin-right: #{math.div($i, -2)}px !important;
& > * {
padding-left: #{math.div($i, 2)}px !important;
padding-right: #{math.div($i, 2)}px !important;
}
}
.gapY-#{$i} {
margin-top: #{math.div($i, -2)}px !important;
margin-bottom: #{math.div($i, -2)}px !important;
& > * {
padding-top: #{math.div($i, 2)}px !important;
padding-bottom: #{math.div($i, 2)}px !important;
}
}
.gap-#{$i} {
margin: #{math.div($i, -2)}px !important;
& > * {
padding: #{math.div($i, 2)}px !important;
}
}
@if ($responsive == true) {
@include generateResponsive() {
.gapX-#{$i}\@#{$breakpointAlias} {
margin-left: #{math.div($i, -2)}px !important;
margin-right: #{math.div($i, -2)}px !important;
& > * {
padding-left: #{math.div($i, 2)}px !important;
padding-right: #{math.div($i, 2)}px !important;
}
}
.gapY-#{$i}\@#{$breakpointAlias} {
margin-top: #{math.div($i, -2)}px !important;
margin-bottom: #{math.div($i, -2)}px !important;
& > * {
padding-top: #{math.div($i, 2)}px !important;
padding-bottom: #{math.div($i, 2)}px !important;
}
}
.gap-#{$i}\@#{$breakpointAlias} {
margin: #{math.div($i, -2)}px !important;
& > * {
padding: #{math.div($i, 2)}px !important;
}
}
}
}
}
}
// ---------------------------------------------------------
// @Gap (40 > 160 Step 10)
// ---------------------------------------------------------
@for $i from 40 through 160 {
@if $i % 10 == 0 {
.gapX-#{$i} {
margin-left: #{math.div($i, -2)}px !important;
margin-right: #{math.div($i, -2)}px !important;
& > * {
padding-left: #{math.div($i, 2)}px !important;
padding-right: #{math.div($i, 2)}px !important;
}
}
.gapY-#{$i} {
margin-top: #{math.div($i, -2)}px !important;
margin-bottom: #{math.div($i, -2)}px !important;
& > * {
padding-top: #{math.div($i, 2)}px !important;
padding-bottom: #{math.div($i, 2)}px !important;
}
}
.gap-#{$i} {
margin: #{math.div($i, -2)}px !important;
& > * {
padding: #{math.div($i, 2)}px !important;
}
}
@if ($responsive == true) {
@include generateResponsive() {
.gapX-#{$i}\@#{$breakpointAlias} {
margin-left: #{math.div($i, -2)}px !important;
margin-right: #{math.div($i, -2)}px !important;
& > * {
padding-left: #{math.div($i, 2)}px !important;
padding-right: #{math.div($i, 2)}px !important;
}
}
.gapY-#{$i}\@#{$breakpointAlias} {
margin-top: #{math.div($i, -2)}px !important;
margin-bottom: #{math.div($i, -2)}px !important;
& > * {
padding-top: #{math.div($i, 2)}px !important;
padding-bottom: #{math.div($i, 2)}px !important;
}
}
.gap-#{$i}\@#{$breakpointAlias} {
margin: #{math.div($i, -2)}px !important;
& > * {
padding: #{math.div($i, 2)}px !important;
}
}
}
}
}
}
================================================
FILE: src/assets/styles/spec/utils/layout/utils/index.scss
================================================
@use 'center' as *;
@use 'gap' as *;
@use 'peers' as *;
@use 'layers' as *;
================================================
FILE: src/assets/styles/spec/utils/layout/utils/layers.scss
================================================
.layers {
display: flex;
flex-flow: column nowrap;
align-items: center;
}
================================================
FILE: src/assets/styles/spec/utils/layout/utils/peers.scss
================================================
.peers {
box-sizing: border-box;
display: flex !important;
align-items: flex-start;
justify-content: flex-start;
flex-flow: row wrap;
height: auto;
max-width: 100%;
margin: 0;
padding: 0;
}
.peer {
display: block;
height: auto;
flex: 0 0 auto;
}
.peer-greed {
flex: 1 1 auto;
// overflow: hidden;
}
.peers-greed > .peer,
.peers-greed > .peers {
flex: 1 1 auto;
}
.peer > img {
max-width: none;
}
.peer-greed > img {
max-width: 100%;
}
================================================
FILE: src/assets/styles/utils/mobile.scss
================================================
// Mobile Utility Classes and Fixes
// This file contains mobile-specific utility classes and responsive fixes
// Mobile text utilities - Only hide theme labels on very small screens
@media (max-width: 480px) {
.theme-toggle .form-check-label {
display: none !important;
}
.theme-toggle {
padding: 0 6px !important;
height: 60px !important; // Match header height
justify-content: center !important;
min-height: 60px !important;
.form-check {
height: 100% !important;
.form-check-label {
font-size: 11px !important; // Slightly smaller for very small screens
font-weight: 500 !important;
i {
font-size: 14px !important; // Still reasonable for tiny screens
}
}
.form-check-input {
margin: 0 6px !important;
width: 38px !important; // Bigger than before but not huge for tiny screens
height: 22px !important; // Bigger than before but not huge for tiny screens
border-radius: 11px !important;
border-width: 2px !important;
}
}
}
// Very small screen adjustments
.d-none-xs {
display: none !important;
}
.fs-xs {
font-size: 10px !important;
}
.p-xs {
padding: 5px !important;
}
.m-xs {
margin: 5px !important;
}
}
// Mobile dropdown improvements
@media (max-width: 767px) {
.dropdown-menu {
// Ensure all dropdowns are mobile-friendly
&.show {
position: fixed !important;
top: 65px !important;
left: 5px !important;
right: 5px !important;
width: auto !important;
transform: none !important;
z-index: 1050;
max-height: calc(100vh - 85px);
overflow-y: auto;
}
}
// Mobile notification improvements
.notifications .dropdown-menu {
.scrollable {
max-height: 300px;
overflow-y: auto;
}
}
}
// Mobile header compact mode
@media (max-width: 991px) {
.header .nav-right > li > a {
padding: 0 6px !important;
}
.header .nav-left > li > a {
padding: 0 8px !important;
}
}
// Ultra-compact mode for very small screens
@media (max-width: 480px) {
.header .nav-right > li > a {
padding: 0 4px !important;
font-size: 14px !important;
}
.header .nav-left > li > a {
padding: 0 6px !important;
}
// Hide search on very small screens when not active
.search-box:not(.active) {
display: none !important;
}
}
// Mobile-specific spacing utilities
.mobile-compact {
@media (max-width: 767px) {
padding: 5px !important;
margin: 2px !important;
}
}
.mobile-hidden {
@media (max-width: 767px) {
display: none !important;
}
}
.mobile-only {
display: none !important;
@media (max-width: 767px) {
display: block !important;
}
}
// Prevent horizontal scroll on mobile
.mobile-no-scroll {
@media (max-width: 767px) {
overflow-x: hidden !important;
}
}
// COMPREHENSIVE Mobile Header Fixes
// Better layout, bigger icons, full-width search, and desktop fixes
// =============================================================================
// DESKTOP FIXES - Remove top spacing issue
// =============================================================================
@media screen and (min-width: 768px) {
.header {
margin-top: 0 !important;
top: 0 !important;
}
.page-container {
padding-top: 61px !important; // Standard header height + 1px border
}
.main-content {
margin-top: 0 !important;
padding-top: 20px !important;
}
// DESKTOP THEME TOGGLE - Make sure it's fully visible
.theme-toggle {
display: flex !important;
align-items: center !important;
height: 65px !important;
padding: 0 15px !important;
.form-check {
margin: 0 !important;
display: flex !important;
align-items: center !important;
.form-check-label {
color: var(--c-text-muted) !important;
font-size: 11px !important;
font-weight: 500 !important;
text-transform: uppercase !important;
letter-spacing: 0.5px !important;
display: inline !important;
i {
font-size: 12px !important;
}
}
.form-check-input {
width: 2.5rem !important;
height: 1.25rem !important;
background-color: var(--c-border) !important;
border: 1px solid var(--c-border) !important;
cursor: pointer !important;
margin: 0 8px !important;
&:checked {
background-color: var(--c-primary) !important;
border-color: var(--c-primary) !important;
}
&:focus {
box-shadow: 0 0 0 0.2rem color-mix(in srgb, var(--c-primary) 25%, transparent) !important;
border-color: var(--c-primary) !important;
}
}
}
}
}
// =============================================================================
// HEADER NAVIGATION MOBILE FIXES - ENHANCED LAYOUT
// =============================================================================
// Mobile header fixes with improved spacing and layout
@media screen and (max-width: 767px) {
// Force header to be fixed and proper height
.header {
position: fixed !important;
top: 0 !important;
left: 0 !important;
right: 0 !important;
z-index: 1000 !important;
width: 100% !important;
height: auto !important;
min-height: 60px !important;
padding: 0 !important;
margin: 0 !important;
margin-top: 0 !important; // Ensure no top margin on mobile
}
// Header container - IMPROVED EDGE-TO-EDGE LAYOUT
.header .header-container {
display: flex !important;
align-items: center !important;
justify-content: space-between !important;
flex-wrap: nowrap !important;
padding: 8px 8px !important; // Reduced side padding for more space
height: auto !important;
min-height: 60px !important;
max-height: 60px !important;
overflow: visible !important;
gap: 12px !important; // Larger gap to push items apart
}
// LEFT SECTION: Logo + Hamburger + Search - PUSHED MORE LEFT
.header .nav-left {
display: flex !important;
align-items: center !important;
flex: 1 1 auto !important;
margin: 0 !important;
padding: 0 !important;
float: none !important;
max-width: 65% !important; // Increased width
gap: 4px !important; // Tighter spacing between left items
justify-content: flex-start !important; // Push to left edge
// Logo first - positioned at far left
&::before {
content: "A" !important;
display: inline-flex !important;
align-items: center !important;
justify-content: center !important;
width: 32px !important;
height: 32px !important;
background: #007bff !important;
color: white !important;
border-radius: 6px !important;
font-weight: bold !important;
font-size: 18px !important;
flex-shrink: 0 !important;
order: -1 !important;
margin-right: 6px !important; // Small margin to separate from hamburger
}
> li {
display: inline-flex !important;
align-items: center !important;
margin: 0 !important;
float: none !important;
> a {
padding: 8px 6px !important; // Reduced padding for tighter spacing
margin: 0 !important;
min-height: auto !important;
line-height: 1 !important;
display: flex !important;
align-items: center !important;
border-radius: 4px !important;
i {
font-size: 20px !important;
margin: 0 !important;
}
// Hover state for better UX
&:hover {
background: rgba(0, 0, 0, 0.05) !important;
}
}
// Sidebar toggle (hamburger)
&:first-child > a {
padding: 8px 6px !important;
i {
font-size: 22px !important;
}
}
// Search toggle
&.search-box > a {
padding: 8px 6px !important;
i {
font-size: 20px !important;
}
}
}
// FULL-WIDTH SEARCH BAR - CLEANER DESIGN
.search-input {
display: none !important; // Hidden by default
position: fixed !important; // Fixed positioning for full control
top: 60px !important; // Right below header
left: 0 !important;
right: 0 !important;
background: var(--c-bkg-card) !important;
border-bottom: 1px solid var(--c-border) !important;
padding: 15px 20px !important; // More padding for better appearance
z-index: 9999 !important;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1) !important; // Subtle shadow
&.active {
display: block !important;
}
input {
width: 100% !important;
padding: 12px 16px !important; // Better padding
font-size: 16px !important;
border: 1px solid var(--c-border) !important;
border-radius: 8px !important; // Rounded corners
background: var(--c-bkg-body) !important;
color: var(--c-text-base) !important;
margin: 0 !important;
outline: none !important;
&::placeholder {
color: var(--c-text-muted) !important;
}
&:focus {
outline: none !important;
border-color: var(--c-primary) !important;
box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25) !important;
}
}
}
}
// RIGHT SECTION: Notifications + Messages + Theme Toggle + Profile - PERFECT ALIGNMENT
.header .nav-right {
display: flex !important;
align-items: center !important;
flex: 0 0 auto !important;
margin: 0 !important;
padding: 0 !important;
float: none !important;
flex-wrap: nowrap !important;
gap: 4px !important; // Consistent spacing
justify-content: flex-end !important; // Push to right edge
height: 60px !important; // Match header height exactly
> li {
display: flex !important;
align-items: center !important;
justify-content: center !important;
margin: 0 !important;
padding: 0 !important;
float: none !important;
flex: 0 0 auto !important;
position: relative !important;
height: 60px !important; // Force exact height for all items
min-height: 60px !important; // Ensure minimum height
> a {
padding: 0 !important; // NO padding for perfect alignment
margin: 0 !important;
width: 44px !important; // Fixed width for all nav items
height: 44px !important; // Fixed height for all nav items
line-height: 1 !important;
display: flex !important;
align-items: center !important; // Perfect vertical centering
justify-content: center !important; // Perfect horizontal centering
position: relative !important;
border-radius: 50% !important; // Circular touch targets
background: transparent !important;
transition: all 0.2s ease !important; // Smooth transitions
i {
font-size: 20px !important; // Consistent icon size for all nav items
margin: 0 !important;
display: block !important;
line-height: 1 !important;
text-align: center !important;
}
// Hide text content, keep only icons
span:not(.counter) {
display: none !important;
}
// Hide user avatar text
.peer:last-child {
display: none !important;
}
// Hover state - subtle and theme-consistent
&:hover {
background: var(--c-grey-100) !important;
transform: scale(1.05) !important;
transition: all 0.2s ease !important;
}
}
// User dropdown - special styling for avatar
&:last-child > a {
.peer {
&:first-child {
margin-right: 0 !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
}
img {
width: 36px !important;
height: 36px !important;
max-width: 36px !important;
max-height: 36px !important;
border-radius: 50% !important;
object-fit: cover !important;
}
}
}
}
// NOTIFICATION COUNTERS - CORRECTLY ANCHORED
> li {
position: relative !important; // This is the anchor
.counter {
position: absolute !important;
top: 10px !important;
right: 10px !important;
z-index: 10 !important;
// The design of the counter itself is inherited
}
}
// Theme toggle - perfectly aligned with other nav icons
.theme-toggle {
padding: 0 !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
height: 60px !important; // Match header height exactly
min-height: 60px !important; // Force exact height
width: 44px !important; // Same width as other nav items
.form-check {
margin: 0 !important;
padding: 0 !important;
align-items: center !important;
justify-content: center !important;
display: flex !important;
height: 44px !important; // Same height as other nav items
width: 44px !important; // Same width as other nav items
border-radius: 50% !important; // Match other nav items
position: relative !important;
.form-check-label {
display: none !important; // Hide labels on mobile for consistency
}
.form-check-input {
width: 28px !important; // Smaller switch to fit in circular area
height: 16px !important; // Smaller switch to fit in circular area
margin: 0 !important; // No margin for perfect centering
flex-shrink: 0 !important;
border-radius: 8px !important; // Proportional border radius
border-width: 1px !important; // Standard border
position: absolute !important;
top: 50% !important;
left: 50% !important;
transform: translate(-50%, -50%) !important; // Perfect centering
}
// Hover state to match other nav items
&:hover {
background: var(--c-grey-100) !important;
transform: scale(1.05) !important;
transition: all 0.2s ease !important;
}
}
}
}
// Full-screen mobile dropdowns
.header .nav-right .dropdown-menu {
position: fixed !important;
top: 60px !important;
left: 0 !important;
right: 0 !important;
bottom: 0 !important;
width: 100vw !important;
height: calc(100vh - 60px) !important;
max-width: none !important;
min-width: auto !important;
transform: none !important;
border-radius: 0 !important;
z-index: 9999 !important;
overflow-y: auto !important;
border: none !important;
margin: 0 !important;
padding: 0 !important;
// Mobile close button
&::before {
content: "✕ Close" !important;
position: sticky !important;
top: 0 !important;
display: block !important;
background: var(--c-primary) !important;
color: white !important;
text-align: center !important;
padding: 15px !important;
cursor: pointer !important;
font-weight: bold !important;
z-index: 10000 !important;
font-size: 16px !important;
}
// UNIFIED EMAIL/NOTIFICATION MOBILE LAYOUT
.peers {
padding: 15px 20px !important;
flex-wrap: wrap !important;
align-items: flex-start !important;
.peer {
max-width: 100% !important;
img {
width: 40px !important;
height: 40px !important;
margin-right: 12px !important;
flex-shrink: 0 !important;
}
&.peer-greed {
flex: 1 !important;
min-width: 0 !important;
// NOTIFICATIONS STYLE - Direct content (span > fw-500 + c-grey-600, then p > small)
> span {
display: block !important;
margin-bottom: 4px !important;
.fw-500 {
font-size: 14px !important;
font-weight: 600 !important;
margin: 0 !important;
color: var(--c-text-base) !important;
display: inline !important;
}
.c-grey-600 {
font-size: 13px !important;
color: var(--c-text-muted) !important;
line-height: 1.4 !important;
display: inline !important;
}
}
> p {
margin: 0 !important;
small {
font-size: 12px !important;
color: var(--c-text-muted) !important;
}
}
// EMAILS STYLE - Match notifications exactly
> div {
// Completely restructure emails to match notification layout
display: block !important;
.peers {
display: inline !important; // Make name and action on same line
margin: 0 !important;
.peer {
&:first-child {
// Name section
p {
font-size: 14px !important;
font-weight: 600 !important;
margin: 0 !important;
color: var(--c-text-base) !important;
display: inline !important;
}
// Add action text after name
&::after {
content: " sent you a message" !important;
font-size: 13px !important;
color: var(--c-text-muted) !important;
font-weight: normal !important;
}
}
&:last-child {
// Move timestamp to its own line below (like notifications)
display: block !important;
margin-top: 2px !important;
small {
font-size: 12px !important;
color: var(--c-text-muted) !important;
display: block !important;
}
}
}
}
// Hide the email preview text completely to match notifications
> .c-grey-600,
.c-grey-600.fsz-sm {
display: none !important;
}
}
}
}
}
// Header items in dropdown
.pX-20 {
padding-left: 20px !important;
padding-right: 20px !important;
.fw-600 {
font-size: 16px !important;
font-weight: 600 !important;
}
}
// Footer links in dropdown
.ta-c {
padding: 15px 20px !important;
a {
font-size: 14px !important;
font-weight: 500 !important;
}
}
}
}
// Extra small screens - refined adjustments
@media screen and (max-width: 479px) {
.header .header-container {
padding: 8px 5px !important; // Even less padding for tiny screens
gap: 8px !important;
}
.header .nav-left {
max-width: 60% !important;
gap: 2px !important; // Even tighter spacing
&::before {
width: 28px !important;
height: 28px !important;
font-size: 16px !important;
margin-right: 4px !important;
}
> li > a {
padding: 8px 4px !important;
i {
font-size: 18px !important;
}
}
}
.header .nav-right {
gap: 1px !important; // Minimal spacing
> li > a {
padding: 8px 4px !important;
i {
font-size: 18px !important; // Slightly smaller for tiny screens
}
}
.theme-toggle .form-check-input {
width: 28px !important;
height: 16px !important;
}
}
// Full-width search bar on tiny screens
.header .nav-left .search-input {
padding: 12px 15px !important;
input {
padding: 10px 12px !important;
font-size: 16px !important; // Prevent zoom on iOS
}
}
}
// =============================================================================
// FOOTER OVERLAP FIXES - MAINTAINING PREVIOUS FIXES
// =============================================================================
// Global layout fixes
html, body {
height: 100% !important;
margin: 0 !important;
padding: 0 !important;
overflow-x: hidden !important;
}
// Page container - force flexbox layout
.page-container {
display: flex !important;
flex-direction: column !important;
min-height: 100vh !important;
margin: 0 !important;
padding-top: 60px !important;
}
// Main content - flexible
.main-content {
flex: 1 0 auto !important;
padding: 20px 10px 40px !important;
margin: 0 !important;
min-height: 0 !important;
overflow-x: hidden !important;
}
// Footer - fixed to bottom
footer {
flex: 0 0 auto !important;
margin-top: auto !important;
padding: 15px 10px !important;
background: var(--c-bkg-card) !important;
border-top: 1px solid var(--c-border) !important;
text-align: center !important;
font-size: 11px !important;
line-height: 1.3 !important;
z-index: 10 !important;
position: relative !important;
width: 100% !important;
clear: both !important;
// CRITICAL: Override lh-0 class that causes overlap
&.lh-0 {
line-height: 1.3 !important; // Force proper line height
}
}
@media screen and (max-width: 767px) {
.page-container {
padding-left: 0 !important;
min-height: 100vh !important; // Ensure full height
position: relative !important; // Ensure positioning context
}
.main-content {
padding: 15px 8px 60px !important; // Increased bottom padding for footer space
margin-bottom: 0 !important;
width: 100% !important;
box-sizing: border-box !important;
}
footer {
position: relative !important; // Ensure footer stays in flow
width: 100% !important;
padding: 15px 10px !important; // Increased padding for better spacing
font-size: 11px !important; // Slightly larger for readability
line-height: 1.4 !important; // Better line height for mobile
background: var(--c-bkg-card) !important;
border-top: 1px solid var(--c-border) !important;
margin-top: auto !important;
box-sizing: border-box !important;
// CRITICAL MOBILE FIXES: Override all conflicting utility classes
&.lh-0 {
line-height: 1.4 !important; // Override line-height: 0 that causes overlap
}
&.p-30 {
padding: 15px 10px !important; // Override desktop padding
}
&.fsz-sm {
font-size: 11px !important; // Ensure readable font size on mobile
}
span {
display: block !important; // Force block for better wrapping
word-wrap: break-word !important;
max-width: 100% !important;
text-align: center !important;
line-height: 1.4 !important;
// Handle long text better
hyphens: auto !important;
word-break: break-word !important;
a {
color: var(--c-primary) !important;
text-decoration: none !important;
display: inline !important; // Keep link inline within text
&:hover {
text-decoration: underline !important;
}
}
}
}
}
@media screen and (max-width: 479px) {
.main-content {
padding: 10px 8px 50px !important; // Adequate bottom space for footer
}
footer {
padding: 12px 8px !important;
font-size: 10px !important;
line-height: 1.5 !important; // Better readability on small screens
// Override utility classes for small screens
&.lh-0 {
line-height: 1.5 !important; // Override line-height: 0
}
&.p-30 {
padding: 12px 8px !important; // Override desktop padding
}
&.fsz-sm {
font-size: 10px !important; // Ensure readable font size
}
span {
display: block !important;
margin: 0 !important; // Remove extra margins
padding: 0 !important;
text-align: center !important;
// Split long copyright text into multiple lines if needed
word-spacing: normal !important;
letter-spacing: normal !important;
// Ensure links are readable
a {
display: inline !important;
white-space: nowrap !important; // Keep "Colorlib" as one word
}
}
}
}
// Additional mobile footer fixes
@media screen and (max-width: 360px) {
footer {
font-size: 9px !important;
padding: 10px 5px !important;
// Override utility classes for extra small screens
&.lh-0 {
line-height: 1.6 !important; // Even better line height for tiny screens
}
&.p-30 {
padding: 10px 5px !important; // Override desktop padding
}
&.fsz-sm {
font-size: 9px !important; // Readable font size for tiny screens
}
span {
line-height: 1.6 !important; // Ensure good readability
// For very small screens, ensure text doesn't overlap
word-break: break-word !important;
overflow-wrap: break-word !important;
// Ensure links are readable
a {
font-weight: bold !important;
color: var(--c-primary) !important;
}
}
}
}
// =============================================================================
// UTILITY CLASSES FOR MOBILE
// =============================================================================
// Prevent body scroll when mobile menus are open
body.mobile-menu-open {
overflow: hidden !important;
position: fixed !important;
width: 100% !important;
}
// Emergency fixes for any remaining issues
@media screen and (max-width: 767px) {
// Ensure dropdowns don't break layout
.dropdown-menu.show {
position: fixed !important;
}
// Prevent content overflow
.row {
margin-left: 0 !important;
margin-right: 0 !important;
}
[class*="col-"] {
padding-left: 5px !important;
padding-right: 5px !important;
}
// Hide on mobile utility
.d-none-mobile {
display: none !important;
}
// Force no horizontal scroll
* {
max-width: 100% !important;
box-sizing: border-box !important;
}
}
================================================
FILE: src/assets/styles/utils/theme.css
================================================
:root {
/* Modern light theme - soft blue-gray tones */
--c-bkg-body: #f0f4f8;
--c-bkg-card: #ffffff;
--c-bkg-sidebar: #ffffff;
--c-bkg-hover: #f8fafc;
--c-text-base: #1e293b;
--c-text-muted: #64748b;
--c-text-light: #94a3b8;
/* Icon colors - modern muted tones */
--c-icon: #64748b;
--c-icon-hover: #6366f1;
--c-icon-active: #4f46e5;
--c-icon-muted: #94a3b8;
/* Modern border with subtle blue tint */
--c-border: #e2e8f0;
--c-border-light: #f1f5f9;
/* Refined brand colors - modern indigo */
--c-primary: #6366f1;
--c-primary-light: #818cf8;
--c-primary-dark: #4f46e5;
--c-success: #10b981;
--c-warning: #f59e0b;
--c-danger: #ef4444;
--c-info: #0ea5e9;
/* Modern shadows */
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
--shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.07), 0 2px 4px -2px rgb(0 0 0 / 0.05);
--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.08), 0 4px 6px -4px rgb(0 0 0 / 0.05);
--shadow-card: 0 1px 3px 0 rgb(0 0 0 / 0.04), 0 1px 2px -1px rgb(0 0 0 / 0.04);
/* Border radius */
--radius-sm: 6px;
--radius-md: 10px;
--radius-lg: 14px;
--radius-xl: 20px;
/* Vector map colors */
--vmap-bg-color: #ffffff;
--vmap-border-color: #ffffff;
--vmap-region-color: #e4ecef;
--vmap-marker-fill: #ffffff;
--vmap-marker-stroke: #000000;
--vmap-hover-color: #ffffff;
--vmap-selected-color: #c9dfaf;
--vmap-scale-start: #03a9f3;
--vmap-scale-end: #02a7f1;
--vmap-scale-light: #b6d6ff;
--vmap-scale-dark: #005ace;
/* Skycons */
--skycons-color: #ff6849;
/* Sparkline colors - more vibrant */
--sparkline-success: #10b981;
--sparkline-purple: #8b5cf6;
--sparkline-info: #0ea5e9;
--sparkline-danger: #ef4444;
--sparkline-light: #a5b4fc;
/* Loader */
--loader-bg: #ffffff;
--spinner-bg: #333333;
/* Google Maps */
--gmap-landscape-hue: #FFBB00;
--gmap-highway-hue: #FFC200;
--gmap-road-hue: #FF0300;
--gmap-water-hue: #0078FF;
--gmap-poi-hue: #00FF6A;
}
[data-theme="dark"] {
/* Modern dark theme - refined dark slate */
--c-bkg-body: #0f172a;
--c-bkg-card: #1e293b;
--c-bkg-sidebar: #1e293b;
--c-bkg-hover: #334155;
--c-text-base: #f1f5f9;
--c-text-muted: #94a3b8;
--c-text-light: #64748b;
/* Icon colors - dark theme */
--c-icon: #94a3b8;
--c-icon-hover: #a5b4fc;
--c-icon-active: #818cf8;
--c-icon-muted: #64748b;
--c-border: #334155;
--c-border-light: #475569;
/* Same primary for consistency, adjusted for dark */
--c-primary: #818cf8;
--c-primary-light: #a5b4fc;
--c-primary-dark: #6366f1;
--c-success: #34d399;
--c-warning: #fbbf24;
--c-danger: #f87171;
--c-info: #38bdf8;
/* Dark mode shadows */
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.3);
--shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.4), 0 2px 4px -2px rgb(0 0 0 / 0.3);
--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.5), 0 4px 6px -4px rgb(0 0 0 / 0.4);
--shadow-card: 0 1px 3px 0 rgb(0 0 0 / 0.2), 0 1px 2px -1px rgb(0 0 0 / 0.2);
/* Vector map colors - dark theme */
--vmap-bg-color: #1e293b;
--vmap-border-color: #334155;
--vmap-region-color: #334155;
--vmap-marker-fill: #f1f5f9;
--vmap-marker-stroke: #94a3b8;
--vmap-hover-color: #0f172a;
--vmap-selected-color: #475569;
--vmap-scale-start: #6366f1;
--vmap-scale-end: #4f46e5;
--vmap-scale-light: #4338ca;
--vmap-scale-dark: #a5b4fc;
/* Skycons - dark theme */
--skycons-color: #fb923c;
/* Sparkline colors - dark theme */
--sparkline-success: #34d399;
--sparkline-purple: #a78bfa;
--sparkline-info: #38bdf8;
--sparkline-danger: #f87171;
--sparkline-light: #a5b4fc;
/* Loader - dark theme */
--loader-bg: #0f172a;
--spinner-bg: #f1f5f9;
/* Google Maps - dark theme */
--gmap-landscape-hue: #D4AC0D;
--gmap-highway-hue: #E6AC00;
--gmap-road-hue: #CC2900;
--gmap-water-hue: #1B4F72;
--gmap-poi-hue: #148F77;
}
================================================
FILE: src/assets/styles/vendor/datepicker.scss
================================================
@use 'sass:color';
@use '../spec/settings/baseColors' as *;
@use '../spec/settings/breakpoints' as *;
@use '../spec/tools/mixins/mediaQueriesRanges' as *;
.datepicker {
border-radius: 0;
padding: 25px;
box-shadow: none;
border: 1px solid $border-color;
table {
tr {
th,
td {
border-radius: 0;
width: 40px;
height: 35px;
}
td {
transition: all 0.2s ease-in-out;
span {
border-radius: 0;
}
}
}
}
}
.datepicker table tr td span.active:active,
.datepicker table tr td span.active:hover:active,
.datepicker table tr td span.active.disabled:active,
.datepicker table tr td span.active.disabled:hover:active,
.datepicker table tr td span.active.active,
.datepicker table tr td span.active:hover.active,
.datepicker table tr td span.active.disabled.active,
.datepicker table tr td span.active.disabled:hover.active,
.datepicker table tr td span.active.active:hover,
.datepicker table tr td span.active:hover.active:hover,
.datepicker table tr td.active:active,
.datepicker table tr td.active.highlighted:active,
.datepicker table tr td.active.active,
.datepicker table tr td.active.highlighted.active,
.datepicker table tr td.active:active:hover,
.datepicker table tr td.active.highlighted:active:hover,
.datepicker table tr td.active.active:hover,
.datepicker table tr td.active.highlighted.active:hover,
.datepicker table tr td.active:active:focus,
.datepicker table tr td.active.highlighted:active:focus,
.datepicker table tr td.active.active:focus,
.datepicker table tr td.active.highlighted.active:focus,
.datepicker table tr td.active:active.focus,
.datepicker table tr td.active.highlighted:active.focus,
.datepicker table tr td.active.active.focus,
.datepicker table tr td.active.highlighted.active.focus {
color: $default-white;
background-color: $default-primary;
border-color: transparent;
}
.datepicker table tr td span:hover,
.datepicker table tr td span.focused {
background: $default-primary;
color: #fff;
}
.datepicker table tr td.day:hover,
.datepicker table tr td.focused {
background: $default-primary;
color: #fff;
cursor: pointer;
}
.datepicker .datepicker-switch:hover,
.datepicker .prev:hover,
.datepicker .next:hover,
.datepicker tfoot tr th:hover {
background: $default-primary;
color: #fff;
cursor: pointer;
}
.datepicker-inline {
width: 330px;
}
.daterangepicker {
border-radius: 0;
padding: 30px;
box-shadow: none;
border: 1px solid $border-color;
.input-mini {
border-radius: 0;
margin-bottom: 20px;
height: 40px;
padding: 0 6px 0 35px;
&.active {
border-radius: 0;
border-color: color.adjust($default-info, $lightness: 20%);
}
}
.daterangepicker_input {
i {
position: absolute;
left: 10px;
top: 13px;
}
}
td,
th {
border-radius: 0;
width: 40px;
height: 35px;
&.available{
&:hover{
background: $default-primary;
color: #fff;
}
}
}
td {
&.in-range {
background-color: transparent;
color: $default-primary;
}
&.active {
background-color: $default-primary;
border-color: transparent;
color: #fff;
&:hover {
background-color: $default-primary;
border-color: transparent;
color: #fff;
}
}
&.start-date {
border-radius: 0;
&.end-date {
border-radius: 0;
}
}
&.end-date {
border-radius: 0;
}
}
select {
&.hourselect,
&.minuteselect,
&.secondselect,
&.ampmselect {
border: 1px solid $border-color;
min-height: 30px;
}
}
.calendar-time {
i {
top: 8px;
left: 35px;
}
}
@include from($breakpoint-sm) {
.calendar {
margin-right: 20px !important;
}
}
}
================================================
FILE: src/assets/styles/vendor/font-awesome.css
================================================
/*!
* Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome
* License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License)
*/
/* FONT PATH
* -------------------------- */
@font-face {
font-family: 'FontAwesome';
src: url('../static/fonts/icons/fontawesome/fontawesome-webfont.eot?v=4.7.0');
src: url('../static/fonts/icons/fontawesome/fontawesome-webfont.eot?#iefix&v=4.7.0') format('embedded-opentype'), url('../static/fonts/icons/fontawesome/fontawesome-webfont.woff2?v=4.7.0') format('woff2'), url('../static/fonts/icons/fontawesome/fontawesome-webfont.woff?v=4.7.0') format('woff'), url('../static/fonts/icons/fontawesome/fontawesome-webfont.ttf?v=4.7.0') format('truetype'), url('../static/fonts/icons/fontawesome/fontawesome-webfont.svg?v=4.7.0#fontawesomeregular') format('svg');
font-weight: normal;
font-style: normal;
}
.fa {
display: inline-block;
font: normal normal normal 14px/1 FontAwesome;
font-size: inherit;
text-rendering: auto;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
/* makes the font 33% larger relative to the icon container */
.fa-lg {
font-size: 1.33333333em;
line-height: 0.75em;
vertical-align: -15%;
}
.fa-2x {
font-size: 2em;
}
.fa-3x {
font-size: 3em;
}
.fa-4x {
font-size: 4em;
}
.fa-5x {
font-size: 5em;
}
.fa-fw {
width: 1.28571429em;
text-align: center;
}
.fa-ul {
padding-left: 0;
margin-left: 2.14285714em;
list-style-type: none;
}
.fa-ul > li {
position: relative;
}
.fa-li {
position: absolute;
left: -2.14285714em;
width: 2.14285714em;
top: 0.14285714em;
text-align: center;
}
.fa-li.fa-lg {
left: -1.85714286em;
}
.fa-border {
padding: .2em .25em .15em;
border: solid 0.08em #eeeeee;
border-radius: .1em;
}
.fa-pull-left {
float: left;
}
.fa-pull-right {
float: right;
}
.fa.fa-pull-left {
margin-right: .3em;
}
.fa.fa-pull-right {
margin-left: .3em;
}
/* Deprecated as of 4.4.0 */
.pull-right {
float: right;
}
.pull-left {
float: left;
}
.fa.pull-left {
margin-right: .3em;
}
.fa.pull-right {
margin-left: .3em;
}
.fa-spin {
-webkit-animation: fa-spin 2s infinite linear;
animation: fa-spin 2s infinite linear;
}
.fa-pulse {
-webkit-animation: fa-spin 1s infinite steps(8);
animation: fa-spin 1s infinite steps(8);
}
@-webkit-keyframes fa-spin {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(359deg);
transform: rotate(359deg);
}
}
@keyframes fa-spin {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(359deg);
transform: rotate(359deg);
}
}
.fa-rotate-90 {
-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";
-webkit-transform: rotate(90deg);
-ms-transform: rotate(90deg);
transform: rotate(90deg);
}
.fa-rotate-180 {
-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";
-webkit-transform: rotate(180deg);
-ms-transform: rotate(180deg);
transform: rotate(180deg);
}
.fa-rotate-270 {
-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";
-webkit-transform: rotate(270deg);
-ms-transform: rotate(270deg);
transform: rotate(270deg);
}
.fa-flip-horizontal {
-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";
-webkit-transform: scale(-1, 1);
-ms-transform: scale(-1, 1);
transform: scale(-1, 1);
}
.fa-flip-vertical {
-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";
-webkit-transform: scale(1, -1);
-ms-transform: scale(1, -1);
transform: scale(1, -1);
}
:root .fa-rotate-90,
:root .fa-rotate-180,
:root .fa-rotate-270,
:root .fa-flip-horizontal,
:root .fa-flip-vertical {
filter: none;
}
.fa-stack {
position: relative;
display: inline-block;
width: 2em;
height: 2em;
line-height: 2em;
vertical-align: middle;
}
.fa-stack-1x,
.fa-stack-2x {
position: absolute;
left: 0;
width: 100%;
text-align: center;
}
.fa-stack-1x {
line-height: inherit;
}
.fa-stack-2x {
font-size: 2em;
}
.fa-inverse {
color: #ffffff;
}
/* Font Awesome uses the Unicode Private Use Area (PUA) to ensure screen
readers do not read off random characters that represent icons */
.fa-glass:before {
content: "\f000";
}
.fa-music:before {
content: "\f001";
}
.fa-search:before {
content: "\f002";
}
.fa-envelope-o:before {
content: "\f003";
}
.fa-heart:before {
content: "\f004";
}
.fa-star:before {
content: "\f005";
}
.fa-star-o:before {
content: "\f006";
}
.fa-user:before {
content: "\f007";
}
.fa-film:before {
content: "\f008";
}
.fa-th-large:before {
content: "\f009";
}
.fa-th:before {
content: "\f00a";
}
.fa-th-list:before {
content: "\f00b";
}
.fa-check:before {
content: "\f00c";
}
.fa-remove:before,
.fa-close:before,
.fa-times:before {
content: "\f00d";
}
.fa-search-plus:before {
content: "\f00e";
}
.fa-search-minus:before {
content: "\f010";
}
.fa-power-off:before {
content: "\f011";
}
.fa-signal:before {
content: "\f012";
}
.fa-gear:before,
.fa-cog:before {
content: "\f013";
}
.fa-trash-o:before {
content: "\f014";
}
.fa-home:before {
content: "\f015";
}
.fa-file-o:before {
content: "\f016";
}
.fa-clock-o:before {
content: "\f017";
}
.fa-road:before {
content: "\f018";
}
.fa-download:before {
content: "\f019";
}
.fa-arrow-circle-o-down:before {
content: "\f01a";
}
.fa-arrow-circle-o-up:before {
content: "\f01b";
}
.fa-inbox:before {
content: "\f01c";
}
.fa-play-circle-o:before {
content: "\f01d";
}
.fa-rotate-right:before,
.fa-repeat:before {
content: "\f01e";
}
.fa-refresh:before {
content: "\f021";
}
.fa-list-alt:before {
content: "\f022";
}
.fa-lock:before {
content: "\f023";
}
.fa-flag:before {
content: "\f024";
}
.fa-headphones:before {
content: "\f025";
}
.fa-volume-off:before {
content: "\f026";
}
.fa-volume-down:before {
content: "\f027";
}
.fa-volume-up:before {
content: "\f028";
}
.fa-qrcode:before {
content: "\f029";
}
.fa-barcode:before {
content: "\f02a";
}
.fa-tag:before {
content: "\f02b";
}
.fa-tags:before {
content: "\f02c";
}
.fa-book:before {
content: "\f02d";
}
.fa-bookmark:before {
content: "\f02e";
}
.fa-print:before {
content: "\f02f";
}
.fa-camera:before {
content: "\f030";
}
.fa-font:before {
content: "\f031";
}
.fa-bold:before {
content: "\f032";
}
.fa-italic:before {
content: "\f033";
}
.fa-text-height:before {
content: "\f034";
}
.fa-text-width:before {
content: "\f035";
}
.fa-align-left:before {
content: "\f036";
}
.fa-align-center:before {
content: "\f037";
}
.fa-align-right:before {
content: "\f038";
}
.fa-align-justify:before {
content: "\f039";
}
.fa-list:before {
content: "\f03a";
}
.fa-dedent:before,
.fa-outdent:before {
content: "\f03b";
}
.fa-indent:before {
content: "\f03c";
}
.fa-video-camera:before {
content: "\f03d";
}
.fa-photo:before,
.fa-image:before,
.fa-picture-o:before {
content: "\f03e";
}
.fa-pencil:before {
content: "\f040";
}
.fa-map-marker:before {
content: "\f041";
}
.fa-adjust:before {
content: "\f042";
}
.fa-tint:before {
content: "\f043";
}
.fa-edit:before,
.fa-pencil-square-o:before {
content: "\f044";
}
.fa-share-square-o:before {
content: "\f045";
}
.fa-check-square-o:before {
content: "\f046";
}
.fa-arrows:before {
content: "\f047";
}
.fa-step-backward:before {
content: "\f048";
}
.fa-fast-backward:before {
content: "\f049";
}
.fa-backward:before {
content: "\f04a";
}
.fa-play:before {
content: "\f04b";
}
.fa-pause:before {
content: "\f04c";
}
.fa-stop:before {
content: "\f04d";
}
.fa-forward:before {
content: "\f04e";
}
.fa-fast-forward:before {
content: "\f050";
}
.fa-step-forward:before {
content: "\f051";
}
.fa-eject:before {
content: "\f052";
}
.fa-chevron-left:before {
content: "\f053";
}
.fa-chevron-right:before {
content: "\f054";
}
.fa-plus-circle:before {
content: "\f055";
}
.fa-minus-circle:before {
content: "\f056";
}
.fa-times-circle:before {
content: "\f057";
}
.fa-check-circle:before {
content: "\f058";
}
.fa-question-circle:before {
content: "\f059";
}
.fa-info-circle:before {
content: "\f05a";
}
.fa-crosshairs:before {
content: "\f05b";
}
.fa-times-circle-o:before {
content: "\f05c";
}
.fa-check-circle-o:before {
content: "\f05d";
}
.fa-ban:before {
content: "\f05e";
}
.fa-arrow-left:before {
content: "\f060";
}
.fa-arrow-right:before {
content: "\f061";
}
.fa-arrow-up:before {
content: "\f062";
}
.fa-arrow-down:before {
content: "\f063";
}
.fa-mail-forward:before,
.fa-share:before {
content: "\f064";
}
.fa-expand:before {
content: "\f065";
}
.fa-compress:before {
content: "\f066";
}
.fa-plus:before {
content: "\f067";
}
.fa-minus:before {
content: "\f068";
}
.fa-asterisk:before {
content: "\f069";
}
.fa-exclamation-circle:before {
content: "\f06a";
}
.fa-gift:before {
content: "\f06b";
}
.fa-leaf:before {
content: "\f06c";
}
.fa-fire:before {
content: "\f06d";
}
.fa-eye:before {
content: "\f06e";
}
.fa-eye-slash:before {
content: "\f070";
}
.fa-warning:before,
.fa-exclamation-triangle:before {
content: "\f071";
}
.fa-plane:before {
content: "\f072";
}
.fa-calendar:before {
content: "\f073";
}
.fa-random:before {
content: "\f074";
}
.fa-comment:before {
content: "\f075";
}
.fa-magnet:before {
content: "\f076";
}
.fa-chevron-up:before {
content: "\f077";
}
.fa-chevron-down:before {
content: "\f078";
}
.fa-retweet:before {
content: "\f079";
}
.fa-shopping-cart:before {
content: "\f07a";
}
.fa-folder:before {
content: "\f07b";
}
.fa-folder-open:before {
content: "\f07c";
}
.fa-arrows-v:before {
content: "\f07d";
}
.fa-arrows-h:before {
content: "\f07e";
}
.fa-bar-chart-o:before,
.fa-bar-chart:before {
content: "\f080";
}
.fa-twitter-square:before {
content: "\f081";
}
.fa-facebook-square:before {
content: "\f082";
}
.fa-camera-retro:before {
content: "\f083";
}
.fa-key:before {
content: "\f084";
}
.fa-gears:before,
.fa-cogs:before {
content: "\f085";
}
.fa-comments:before {
content: "\f086";
}
.fa-thumbs-o-up:before {
content: "\f087";
}
.fa-thumbs-o-down:before {
content: "\f088";
}
.fa-star-half:before {
content: "\f089";
}
.fa-heart-o:before {
content: "\f08a";
}
.fa-sign-out:before {
content: "\f08b";
}
.fa-linkedin-square:before {
content: "\f08c";
}
.fa-thumb-tack:before {
content: "\f08d";
}
.fa-external-link:before {
content: "\f08e";
}
.fa-sign-in:before {
content: "\f090";
}
.fa-trophy:before {
content: "\f091";
}
.fa-github-square:before {
content: "\f092";
}
.fa-upload:before {
content: "\f093";
}
.fa-lemon-o:before {
content: "\f094";
}
.fa-phone:before {
content: "\f095";
}
.fa-square-o:before {
content: "\f096";
}
.fa-bookmark-o:before {
content: "\f097";
}
.fa-phone-square:before {
content: "\f098";
}
.fa-twitter:before {
content: "\f099";
}
.fa-facebook-f:before,
.fa-facebook:before {
content: "\f09a";
}
.fa-github:before {
content: "\f09b";
}
.fa-unlock:before {
content: "\f09c";
}
.fa-credit-card:before {
content: "\f09d";
}
.fa-feed:before,
.fa-rss:before {
content: "\f09e";
}
.fa-hdd-o:before {
content: "\f0a0";
}
.fa-bullhorn:before {
content: "\f0a1";
}
.fa-bell:before {
content: "\f0f3";
}
.fa-certificate:before {
content: "\f0a3";
}
.fa-hand-o-right:before {
content: "\f0a4";
}
.fa-hand-o-left:before {
content: "\f0a5";
}
.fa-hand-o-up:before {
content: "\f0a6";
}
.fa-hand-o-down:before {
content: "\f0a7";
}
.fa-arrow-circle-left:before {
content: "\f0a8";
}
.fa-arrow-circle-right:before {
content: "\f0a9";
}
.fa-arrow-circle-up:before {
content: "\f0aa";
}
.fa-arrow-circle-down:before {
content: "\f0ab";
}
.fa-globe:before {
content: "\f0ac";
}
.fa-wrench:before {
content: "\f0ad";
}
.fa-tasks:before {
content: "\f0ae";
}
.fa-filter:before {
content: "\f0b0";
}
.fa-briefcase:before {
content: "\f0b1";
}
.fa-arrows-alt:before {
content: "\f0b2";
}
.fa-group:before,
.fa-users:before {
content: "\f0c0";
}
.fa-chain:before,
.fa-link:before {
content: "\f0c1";
}
.fa-cloud:before {
content: "\f0c2";
}
.fa-flask:before {
content: "\f0c3";
}
.fa-cut:before,
.fa-scissors:before {
content: "\f0c4";
}
.fa-copy:before,
.fa-files-o:before {
content: "\f0c5";
}
.fa-paperclip:before {
content: "\f0c6";
}
.fa-save:before,
.fa-floppy-o:before {
content: "\f0c7";
}
.fa-square:before {
content: "\f0c8";
}
.fa-navicon:before,
.fa-reorder:before,
.fa-bars:before {
content: "\f0c9";
}
.fa-list-ul:before {
content: "\f0ca";
}
.fa-list-ol:before {
content: "\f0cb";
}
.fa-strikethrough:before {
content: "\f0cc";
}
.fa-underline:before {
content: "\f0cd";
}
.fa-table:before {
content: "\f0ce";
}
.fa-magic:before {
content: "\f0d0";
}
.fa-truck:before {
content: "\f0d1";
}
.fa-pinterest:before {
content: "\f0d2";
}
.fa-pinterest-square:before {
content: "\f0d3";
}
.fa-google-plus-square:before {
content: "\f0d4";
}
.fa-google-plus:before {
content: "\f0d5";
}
.fa-money:before {
content: "\f0d6";
}
.fa-caret-down:before {
content: "\f0d7";
}
.fa-caret-up:before {
content: "\f0d8";
}
.fa-caret-left:before {
content: "\f0d9";
}
.fa-caret-right:before {
content: "\f0da";
}
.fa-columns:before {
content: "\f0db";
}
.fa-unsorted:before,
.fa-sort:before {
content: "\f0dc";
}
.fa-sort-down:before,
.fa-sort-desc:before {
content: "\f0dd";
}
.fa-sort-up:before,
.fa-sort-asc:before {
content: "\f0de";
}
.fa-envelope:before {
content: "\f0e0";
}
.fa-linkedin:before {
content: "\f0e1";
}
.fa-rotate-left:before,
.fa-undo:before {
content: "\f0e2";
}
.fa-legal:before,
.fa-gavel:before {
content: "\f0e3";
}
.fa-dashboard:before,
.fa-tachometer:before {
content: "\f0e4";
}
.fa-comment-o:before {
content: "\f0e5";
}
.fa-comments-o:before {
content: "\f0e6";
}
.fa-flash:before,
.fa-bolt:before {
content: "\f0e7";
}
.fa-sitemap:before {
content: "\f0e8";
}
.fa-umbrella:before {
content: "\f0e9";
}
.fa-paste:before,
.fa-clipboard:before {
content: "\f0ea";
}
.fa-lightbulb-o:before {
content: "\f0eb";
}
.fa-exchange:before {
content: "\f0ec";
}
.fa-cloud-download:before {
content: "\f0ed";
}
.fa-cloud-upload:before {
content: "\f0ee";
}
.fa-user-md:before {
content: "\f0f0";
}
.fa-stethoscope:before {
content: "\f0f1";
}
.fa-suitcase:before {
content: "\f0f2";
}
.fa-bell-o:before {
content: "\f0a2";
}
.fa-coffee:before {
content: "\f0f4";
}
.fa-cutlery:before {
content: "\f0f5";
}
.fa-file-text-o:before {
content: "\f0f6";
}
.fa-building-o:before {
content: "\f0f7";
}
.fa-hospital-o:before {
content: "\f0f8";
}
.fa-ambulance:before {
content: "\f0f9";
}
.fa-medkit:before {
content: "\f0fa";
}
.fa-fighter-jet:before {
content: "\f0fb";
}
.fa-beer:before {
content: "\f0fc";
}
.fa-h-square:before {
content: "\f0fd";
}
.fa-plus-square:before {
content: "\f0fe";
}
.fa-angle-double-left:before {
content: "\f100";
}
.fa-angle-double-right:before {
content: "\f101";
}
.fa-angle-double-up:before {
content: "\f102";
}
.fa-angle-double-down:before {
content: "\f103";
}
.fa-angle-left:before {
content: "\f104";
}
.fa-angle-right:before {
content: "\f105";
}
.fa-angle-up:before {
content: "\f106";
}
.fa-angle-down:before {
content: "\f107";
}
.fa-desktop:before {
content: "\f108";
}
.fa-laptop:before {
content: "\f109";
}
.fa-tablet:before {
content: "\f10a";
}
.fa-mobile-phone:before,
.fa-mobile:before {
content: "\f10b";
}
.fa-circle-o:before {
content: "\f10c";
}
.fa-quote-left:before {
content: "\f10d";
}
.fa-quote-right:before {
content: "\f10e";
}
.fa-spinner:before {
content: "\f110";
}
.fa-circle:before {
content: "\f111";
}
.fa-mail-reply:before,
.fa-reply:before {
content: "\f112";
}
.fa-github-alt:before {
content: "\f113";
}
.fa-folder-o:before {
content: "\f114";
}
.fa-folder-open-o:before {
content: "\f115";
}
.fa-smile-o:before {
content: "\f118";
}
.fa-frown-o:before {
content: "\f119";
}
.fa-meh-o:before {
content: "\f11a";
}
.fa-gamepad:before {
content: "\f11b";
}
.fa-keyboard-o:before {
content: "\f11c";
}
.fa-flag-o:before {
content: "\f11d";
}
.fa-flag-checkered:before {
content: "\f11e";
}
.fa-terminal:before {
content: "\f120";
}
.fa-code:before {
content: "\f121";
}
.fa-mail-reply-all:before,
.fa-reply-all:before {
content: "\f122";
}
.fa-star-half-empty:before,
.fa-star-half-full:before,
.fa-star-half-o:before {
content: "\f123";
}
.fa-location-arrow:before {
content: "\f124";
}
.fa-crop:before {
content: "\f125";
}
.fa-code-fork:before {
content: "\f126";
}
.fa-unlink:before,
.fa-chain-broken:before {
content: "\f127";
}
.fa-question:before {
content: "\f128";
}
.fa-info:before {
content: "\f129";
}
.fa-exclamation:before {
content: "\f12a";
}
.fa-superscript:before {
content: "\f12b";
}
.fa-subscript:before {
content: "\f12c";
}
.fa-eraser:before {
content: "\f12d";
}
.fa-puzzle-piece:before {
content: "\f12e";
}
.fa-microphone:before {
content: "\f130";
}
.fa-microphone-slash:before {
content: "\f131";
}
.fa-shield:before {
content: "\f132";
}
.fa-calendar-o:before {
content: "\f133";
}
.fa-fire-extinguisher:before {
content: "\f134";
}
.fa-rocket:before {
content: "\f135";
}
.fa-maxcdn:before {
content: "\f136";
}
.fa-chevron-circle-left:before {
content: "\f137";
}
.fa-chevron-circle-right:before {
content: "\f138";
}
.fa-chevron-circle-up:before {
content: "\f139";
}
.fa-chevron-circle-down:before {
content: "\f13a";
}
.fa-html5:before {
content: "\f13b";
}
.fa-css3:before {
content: "\f13c";
}
.fa-anchor:before {
content: "\f13d";
}
.fa-unlock-alt:before {
content: "\f13e";
}
.fa-bullseye:before {
content: "\f140";
}
.fa-ellipsis-h:before {
content: "\f141";
}
.fa-ellipsis-v:before {
content: "\f142";
}
.fa-rss-square:before {
content: "\f143";
}
.fa-play-circle:before {
content: "\f144";
}
.fa-ticket:before {
content: "\f145";
}
.fa-minus-square:before {
content: "\f146";
}
.fa-minus-square-o:before {
content: "\f147";
}
.fa-level-up:before {
content: "\f148";
}
.fa-level-down:before {
content: "\f149";
}
.fa-check-square:before {
content: "\f14a";
}
.fa-pencil-square:before {
content: "\f14b";
}
.fa-external-link-square:before {
content: "\f14c";
}
.fa-share-square:before {
content: "\f14d";
}
.fa-compass:before {
content: "\f14e";
}
.fa-toggle-down:before,
.fa-caret-square-o-down:before {
content: "\f150";
}
.fa-toggle-up:before,
.fa-caret-square-o-up:before {
content: "\f151";
}
.fa-toggle-right:before,
.fa-caret-square-o-right:before {
content: "\f152";
}
.fa-euro:before,
.fa-eur:before {
content: "\f153";
}
.fa-gbp:before {
content: "\f154";
}
.fa-dollar:before,
.fa-usd:before {
content: "\f155";
}
.fa-rupee:before,
.fa-inr:before {
content: "\f156";
}
.fa-cny:before,
.fa-rmb:before,
.fa-yen:before,
.fa-jpy:before {
content: "\f157";
}
.fa-ruble:before,
.fa-rouble:before,
.fa-rub:before {
content: "\f158";
}
.fa-won:before,
.fa-krw:before {
content: "\f159";
}
.fa-bitcoin:before,
.fa-btc:before {
content: "\f15a";
}
.fa-file:before {
content: "\f15b";
}
.fa-file-text:before {
content: "\f15c";
}
.fa-sort-alpha-asc:before {
content: "\f15d";
}
.fa-sort-alpha-desc:before {
content: "\f15e";
}
.fa-sort-amount-asc:before {
content: "\f160";
}
.fa-sort-amount-desc:before {
content: "\f161";
}
.fa-sort-numeric-asc:before {
content: "\f162";
}
.fa-sort-numeric-desc:before {
content: "\f163";
}
.fa-thumbs-up:before {
content: "\f164";
}
.fa-thumbs-down:before {
content: "\f165";
}
.fa-youtube-square:before {
content: "\f166";
}
.fa-youtube:before {
content: "\f167";
}
.fa-xing:before {
content: "\f168";
}
.fa-xing-square:before {
content: "\f169";
}
.fa-youtube-play:before {
content: "\f16a";
}
.fa-dropbox:before {
content: "\f16b";
}
.fa-stack-overflow:before {
content: "\f16c";
}
.fa-instagram:before {
content: "\f16d";
}
.fa-flickr:before {
content: "\f16e";
}
.fa-adn:before {
content: "\f170";
}
.fa-bitbucket:before {
content: "\f171";
}
.fa-bitbucket-square:before {
content: "\f172";
}
.fa-tumblr:before {
content: "\f173";
}
.fa-tumblr-square:before {
content: "\f174";
}
.fa-long-arrow-down:before {
content: "\f175";
}
.fa-long-arrow-up:before {
content: "\f176";
}
.fa-long-arrow-left:before {
content: "\f177";
}
.fa-long-arrow-right:before {
content: "\f178";
}
.fa-apple:before {
content: "\f179";
}
.fa-windows:before {
content: "\f17a";
}
.fa-android:before {
content: "\f17b";
}
.fa-linux:before {
content: "\f17c";
}
.fa-dribbble:before {
content: "\f17d";
}
.fa-skype:before {
content: "\f17e";
}
.fa-foursquare:before {
content: "\f180";
}
.fa-trello:before {
content: "\f181";
}
.fa-female:before {
content: "\f182";
}
.fa-male:before {
content: "\f183";
}
.fa-gittip:before,
.fa-gratipay:before {
content: "\f184";
}
.fa-sun-o:before {
content: "\f185";
}
.fa-moon-o:before {
content: "\f186";
}
.fa-archive:before {
content: "\f187";
}
.fa-bug:before {
content: "\f188";
}
.fa-vk:before {
content: "\f189";
}
.fa-weibo:before {
content: "\f18a";
}
.fa-renren:before {
content: "\f18b";
}
.fa-pagelines:before {
content: "\f18c";
}
.fa-stack-exchange:before {
content: "\f18d";
}
.fa-arrow-circle-o-right:before {
content: "\f18e";
}
.fa-arrow-circle-o-left:before {
content: "\f190";
}
.fa-toggle-left:before,
.fa-caret-square-o-left:before {
content: "\f191";
}
.fa-dot-circle-o:before {
content: "\f192";
}
.fa-wheelchair:before {
content: "\f193";
}
.fa-vimeo-square:before {
content: "\f194";
}
.fa-turkish-lira:before,
.fa-try:before {
content: "\f195";
}
.fa-plus-square-o:before {
content: "\f196";
}
.fa-space-shuttle:before {
content: "\f197";
}
.fa-slack:before {
content: "\f198";
}
.fa-envelope-square:before {
content: "\f199";
}
.fa-wordpress:before {
content: "\f19a";
}
.fa-openid:before {
content: "\f19b";
}
.fa-institution:before,
.fa-bank:before,
.fa-university:before {
content: "\f19c";
}
.fa-mortar-board:before,
.fa-graduation-cap:before {
content: "\f19d";
}
.fa-yahoo:before {
content: "\f19e";
}
.fa-google:before {
content: "\f1a0";
}
.fa-reddit:before {
content: "\f1a1";
}
.fa-reddit-square:before {
content: "\f1a2";
}
.fa-stumbleupon-circle:before {
content: "\f1a3";
}
.fa-stumbleupon:before {
content: "\f1a4";
}
.fa-delicious:before {
content: "\f1a5";
}
.fa-digg:before {
content: "\f1a6";
}
.fa-pied-piper-pp:before {
content: "\f1a7";
}
.fa-pied-piper-alt:before {
content: "\f1a8";
}
.fa-drupal:before {
content: "\f1a9";
}
.fa-joomla:before {
content: "\f1aa";
}
.fa-language:before {
content: "\f1ab";
}
.fa-fax:before {
content: "\f1ac";
}
.fa-building:before {
content: "\f1ad";
}
.fa-child:before {
content: "\f1ae";
}
.fa-paw:before {
content: "\f1b0";
}
.fa-spoon:before {
content: "\f1b1";
}
.fa-cube:before {
content: "\f1b2";
}
.fa-cubes:before {
content: "\f1b3";
}
.fa-behance:before {
content: "\f1b4";
}
.fa-behance-square:before {
content: "\f1b5";
}
.fa-steam:before {
content: "\f1b6";
}
.fa-steam-square:before {
content: "\f1b7";
}
.fa-recycle:before {
content: "\f1b8";
}
.fa-automobile:before,
.fa-car:before {
content: "\f1b9";
}
.fa-cab:before,
.fa-taxi:before {
content: "\f1ba";
}
.fa-tree:before {
content: "\f1bb";
}
.fa-spotify:before {
content: "\f1bc";
}
.fa-deviantart:before {
content: "\f1bd";
}
.fa-soundcloud:before {
content: "\f1be";
}
.fa-database:before {
content: "\f1c0";
}
.fa-file-pdf-o:before {
content: "\f1c1";
}
.fa-file-word-o:before {
content: "\f1c2";
}
.fa-file-excel-o:before {
content: "\f1c3";
}
.fa-file-powerpoint-o:before {
content: "\f1c4";
}
.fa-file-photo-o:before,
.fa-file-picture-o:before,
.fa-file-image-o:before {
content: "\f1c5";
}
.fa-file-zip-o:before,
.fa-file-archive-o:before {
content: "\f1c6";
}
.fa-file-sound-o:before,
.fa-file-audio-o:before {
content: "\f1c7";
}
.fa-file-movie-o:before,
.fa-file-video-o:before {
content: "\f1c8";
}
.fa-file-code-o:before {
content: "\f1c9";
}
.fa-vine:before {
content: "\f1ca";
}
.fa-codepen:before {
content: "\f1cb";
}
.fa-jsfiddle:before {
content: "\f1cc";
}
.fa-life-bouy:before,
.fa-life-buoy:before,
.fa-life-saver:before,
.fa-support:before,
.fa-life-ring:before {
content: "\f1cd";
}
.fa-circle-o-notch:before {
content: "\f1ce";
}
.fa-ra:before,
.fa-resistance:before,
.fa-rebel:before {
content: "\f1d0";
}
.fa-ge:before,
.fa-empire:before {
content: "\f1d1";
}
.fa-git-square:before {
content: "\f1d2";
}
.fa-git:before {
content: "\f1d3";
}
.fa-y-combinator-square:before,
.fa-yc-square:before,
.fa-hacker-news:before {
content: "\f1d4";
}
.fa-tencent-weibo:before {
content: "\f1d5";
}
.fa-qq:before {
content: "\f1d6";
}
.fa-wechat:before,
.fa-weixin:before {
content: "\f1d7";
}
.fa-send:before,
.fa-paper-plane:before {
content: "\f1d8";
}
.fa-send-o:before,
.fa-paper-plane-o:before {
content: "\f1d9";
}
.fa-history:before {
content: "\f1da";
}
.fa-circle-thin:before {
content: "\f1db";
}
.fa-header:before {
content: "\f1dc";
}
.fa-paragraph:before {
content: "\f1dd";
}
.fa-sliders:before {
content: "\f1de";
}
.fa-share-alt:before {
content: "\f1e0";
}
.fa-share-alt-square:before {
content: "\f1e1";
}
.fa-bomb:before {
content: "\f1e2";
}
.fa-soccer-ball-o:before,
.fa-futbol-o:before {
content: "\f1e3";
}
.fa-tty:before {
content: "\f1e4";
}
.fa-binoculars:before {
content: "\f1e5";
}
.fa-plug:before {
content: "\f1e6";
}
.fa-slideshare:before {
content: "\f1e7";
}
.fa-twitch:before {
content: "\f1e8";
}
.fa-yelp:before {
content: "\f1e9";
}
.fa-newspaper-o:before {
content: "\f1ea";
}
.fa-wifi:before {
content: "\f1eb";
}
.fa-calculator:before {
content: "\f1ec";
}
.fa-paypal:before {
content: "\f1ed";
}
.fa-google-wallet:before {
content: "\f1ee";
}
.fa-cc-visa:before {
content: "\f1f0";
}
.fa-cc-mastercard:before {
content: "\f1f1";
}
.fa-cc-discover:before {
content: "\f1f2";
}
.fa-cc-amex:before {
content: "\f1f3";
}
.fa-cc-paypal:before {
content: "\f1f4";
}
.fa-cc-stripe:before {
content: "\f1f5";
}
.fa-bell-slash:before {
content: "\f1f6";
}
.fa-bell-slash-o:before {
content: "\f1f7";
}
.fa-trash:before {
content: "\f1f8";
}
.fa-copyright:before {
content: "\f1f9";
}
.fa-at:before {
content: "\f1fa";
}
.fa-eyedropper:before {
content: "\f1fb";
}
.fa-paint-brush:before {
content: "\f1fc";
}
.fa-birthday-cake:before {
content: "\f1fd";
}
.fa-area-chart:before {
content: "\f1fe";
}
.fa-pie-chart:before {
content: "\f200";
}
.fa-line-chart:before {
content: "\f201";
}
.fa-lastfm:before {
content: "\f202";
}
.fa-lastfm-square:before {
content: "\f203";
}
.fa-toggle-off:before {
content: "\f204";
}
.fa-toggle-on:before {
content: "\f205";
}
.fa-bicycle:before {
content: "\f206";
}
.fa-bus:before {
content: "\f207";
}
.fa-ioxhost:before {
content: "\f208";
}
.fa-angellist:before {
content: "\f209";
}
.fa-cc:before {
content: "\f20a";
}
.fa-shekel:before,
.fa-sheqel:before,
.fa-ils:before {
content: "\f20b";
}
.fa-meanpath:before {
content: "\f20c";
}
.fa-buysellads:before {
content: "\f20d";
}
.fa-connectdevelop:before {
content: "\f20e";
}
.fa-dashcube:before {
content: "\f210";
}
.fa-forumbee:before {
content: "\f211";
}
.fa-leanpub:before {
content: "\f212";
}
.fa-sellsy:before {
content: "\f213";
}
.fa-shirtsinbulk:before {
content: "\f214";
}
.fa-simplybuilt:before {
content: "\f215";
}
.fa-skyatlas:before {
content: "\f216";
}
.fa-cart-plus:before {
content: "\f217";
}
.fa-cart-arrow-down:before {
content: "\f218";
}
.fa-diamond:before {
content: "\f219";
}
.fa-ship:before {
content: "\f21a";
}
.fa-user-secret:before {
content: "\f21b";
}
.fa-motorcycle:before {
content: "\f21c";
}
.fa-street-view:before {
content: "\f21d";
}
.fa-heartbeat:before {
content: "\f21e";
}
.fa-venus:before {
content: "\f221";
}
.fa-mars:before {
content: "\f222";
}
.fa-mercury:before {
content: "\f223";
}
.fa-intersex:before,
.fa-transgender:before {
content: "\f224";
}
.fa-transgender-alt:before {
content: "\f225";
}
.fa-venus-double:before {
content: "\f226";
}
.fa-mars-double:before {
content: "\f227";
}
.fa-venus-mars:before {
content: "\f228";
}
.fa-mars-stroke:before {
content: "\f229";
}
.fa-mars-stroke-v:before {
content: "\f22a";
}
.fa-mars-stroke-h:before {
content: "\f22b";
}
.fa-neuter:before {
content: "\f22c";
}
.fa-genderless:before {
content: "\f22d";
}
.fa-facebook-official:before {
content: "\f230";
}
.fa-pinterest-p:before {
content: "\f231";
}
.fa-whatsapp:before {
content: "\f232";
}
.fa-server:before {
content: "\f233";
}
.fa-user-plus:before {
content: "\f234";
}
.fa-user-times:before {
content: "\f235";
}
.fa-hotel:before,
.fa-bed:before {
content: "\f236";
}
.fa-viacoin:before {
content: "\f237";
}
.fa-train:before {
content: "\f238";
}
.fa-subway:before {
content: "\f239";
}
.fa-medium:before {
content: "\f23a";
}
.fa-yc:before,
.fa-y-combinator:before {
content: "\f23b";
}
.fa-optin-monster:before {
content: "\f23c";
}
.fa-opencart:before {
content: "\f23d";
}
.fa-expeditedssl:before {
content: "\f23e";
}
.fa-battery-4:before,
.fa-battery:before,
.fa-battery-full:before {
content: "\f240";
}
.fa-battery-3:before,
.fa-battery-three-quarters:before {
content: "\f241";
}
.fa-battery-2:before,
.fa-battery-half:before {
content: "\f242";
}
.fa-battery-1:before,
.fa-battery-quarter:before {
content: "\f243";
}
.fa-battery-0:before,
.fa-battery-empty:before {
content: "\f244";
}
.fa-mouse-pointer:before {
content: "\f245";
}
.fa-i-cursor:before {
content: "\f246";
}
.fa-object-group:before {
content: "\f247";
}
.fa-object-ungroup:before {
content: "\f248";
}
.fa-sticky-note:before {
content: "\f249";
}
.fa-sticky-note-o:before {
content: "\f24a";
}
.fa-cc-jcb:before {
content: "\f24b";
}
.fa-cc-diners-club:before {
content: "\f24c";
}
.fa-clone:before {
content: "\f24d";
}
.fa-balance-scale:before {
content: "\f24e";
}
.fa-hourglass-o:before {
content: "\f250";
}
.fa-hourglass-1:before,
.fa-hourglass-start:before {
content: "\f251";
}
.fa-hourglass-2:before,
.fa-hourglass-half:before {
content: "\f252";
}
.fa-hourglass-3:before,
.fa-hourglass-end:before {
content: "\f253";
}
.fa-hourglass:before {
content: "\f254";
}
.fa-hand-grab-o:before,
.fa-hand-rock-o:before {
content: "\f255";
}
.fa-hand-stop-o:before,
.fa-hand-paper-o:before {
content: "\f256";
}
.fa-hand-scissors-o:before {
content: "\f257";
}
.fa-hand-lizard-o:before {
content: "\f258";
}
.fa-hand-spock-o:before {
content: "\f259";
}
.fa-hand-pointer-o:before {
content: "\f25a";
}
.fa-hand-peace-o:before {
content: "\f25b";
}
.fa-trademark:before {
content: "\f25c";
}
.fa-registered:before {
content: "\f25d";
}
.fa-creative-commons:before {
content: "\f25e";
}
.fa-gg:before {
content: "\f260";
}
.fa-gg-circle:before {
content: "\f261";
}
.fa-tripadvisor:before {
content: "\f262";
}
.fa-odnoklassniki:before {
content: "\f263";
}
.fa-odnoklassniki-square:before {
content: "\f264";
}
.fa-get-pocket:before {
content: "\f265";
}
.fa-wikipedia-w:before {
content: "\f266";
}
.fa-safari:before {
content: "\f267";
}
.fa-chrome:before {
content: "\f268";
}
.fa-firefox:before {
content: "\f269";
}
.fa-opera:before {
content: "\f26a";
}
.fa-internet-explorer:before {
content: "\f26b";
}
.fa-tv:before,
.fa-television:before {
content: "\f26c";
}
.fa-contao:before {
content: "\f26d";
}
.fa-500px:before {
content: "\f26e";
}
.fa-amazon:before {
content: "\f270";
}
.fa-calendar-plus-o:before {
content: "\f271";
}
.fa-calendar-minus-o:before {
content: "\f272";
}
.fa-calendar-times-o:before {
content: "\f273";
}
.fa-calendar-check-o:before {
content: "\f274";
}
.fa-industry:before {
content: "\f275";
}
.fa-map-pin:before {
content: "\f276";
}
.fa-map-signs:before {
content: "\f277";
}
.fa-map-o:before {
content: "\f278";
}
.fa-map:before {
content: "\f279";
}
.fa-commenting:before {
content: "\f27a";
}
.fa-commenting-o:before {
content: "\f27b";
}
.fa-houzz:before {
content: "\f27c";
}
.fa-vimeo:before {
content: "\f27d";
}
.fa-black-tie:before {
content: "\f27e";
}
.fa-fonticons:before {
content: "\f280";
}
.fa-reddit-alien:before {
content: "\f281";
}
.fa-edge:before {
content: "\f282";
}
.fa-credit-card-alt:before {
content: "\f283";
}
.fa-codiepie:before {
content: "\f284";
}
.fa-modx:before {
content: "\f285";
}
.fa-fort-awesome:before {
content: "\f286";
}
.fa-usb:before {
content: "\f287";
}
.fa-product-hunt:before {
content: "\f288";
}
.fa-mixcloud:before {
content: "\f289";
}
.fa-scribd:before {
content: "\f28a";
}
.fa-pause-circle:before {
content: "\f28b";
}
.fa-pause-circle-o:before {
content: "\f28c";
}
.fa-stop-circle:before {
content: "\f28d";
}
.fa-stop-circle-o:before {
content: "\f28e";
}
.fa-shopping-bag:before {
content: "\f290";
}
.fa-shopping-basket:before {
content: "\f291";
}
.fa-hashtag:before {
content: "\f292";
}
.fa-bluetooth:before {
content: "\f293";
}
.fa-bluetooth-b:before {
content: "\f294";
}
.fa-percent:before {
content: "\f295";
}
.fa-gitlab:before {
content: "\f296";
}
.fa-wpbeginner:before {
content: "\f297";
}
.fa-wpforms:before {
content: "\f298";
}
.fa-envira:before {
content: "\f299";
}
.fa-universal-access:before {
content: "\f29a";
}
.fa-wheelchair-alt:before {
content: "\f29b";
}
.fa-question-circle-o:before {
content: "\f29c";
}
.fa-blind:before {
content: "\f29d";
}
.fa-audio-description:before {
content: "\f29e";
}
.fa-volume-control-phone:before {
content: "\f2a0";
}
.fa-braille:before {
content: "\f2a1";
}
.fa-assistive-listening-systems:before {
content: "\f2a2";
}
.fa-asl-interpreting:before,
.fa-american-sign-language-interpreting:before {
content: "\f2a3";
}
.fa-deafness:before,
.fa-hard-of-hearing:before,
.fa-deaf:before {
content: "\f2a4";
}
.fa-glide:before {
content: "\f2a5";
}
.fa-glide-g:before {
content: "\f2a6";
}
.fa-signing:before,
.fa-sign-language:before {
content: "\f2a7";
}
.fa-low-vision:before {
content: "\f2a8";
}
.fa-viadeo:before {
content: "\f2a9";
}
.fa-viadeo-square:before {
content: "\f2aa";
}
.fa-snapchat:before {
content: "\f2ab";
}
.fa-snapchat-ghost:before {
content: "\f2ac";
}
.fa-snapchat-square:before {
content: "\f2ad";
}
.fa-pied-piper:before {
content: "\f2ae";
}
.fa-first-order:before {
content: "\f2b0";
}
.fa-yoast:before {
content: "\f2b1";
}
.fa-themeisle:before {
content: "\f2b2";
}
.fa-google-plus-circle:before,
.fa-google-plus-official:before {
content: "\f2b3";
}
.fa-fa:before,
.fa-font-awesome:before {
content: "\f2b4";
}
.fa-handshake-o:before {
content: "\f2b5";
}
.fa-envelope-open:before {
content: "\f2b6";
}
.fa-envelope-open-o:before {
content: "\f2b7";
}
.fa-linode:before {
content: "\f2b8";
}
.fa-address-book:before {
content: "\f2b9";
}
.fa-address-book-o:before {
content: "\f2ba";
}
.fa-vcard:before,
.fa-address-card:before {
content: "\f2bb";
}
.fa-vcard-o:before,
.fa-address-card-o:before {
content: "\f2bc";
}
.fa-user-circle:before {
content: "\f2bd";
}
.fa-user-circle-o:before {
content: "\f2be";
}
.fa-user-o:before {
content: "\f2c0";
}
.fa-id-badge:before {
content: "\f2c1";
}
.fa-drivers-license:before,
.fa-id-card:before {
content: "\f2c2";
}
.fa-drivers-license-o:before,
.fa-id-card-o:before {
content: "\f2c3";
}
.fa-quora:before {
content: "\f2c4";
}
.fa-free-code-camp:before {
content: "\f2c5";
}
.fa-telegram:before {
content: "\f2c6";
}
.fa-thermometer-4:before,
.fa-thermometer:before,
.fa-thermometer-full:before {
content: "\f2c7";
}
.fa-thermometer-3:before,
.fa-thermometer-three-quarters:before {
content: "\f2c8";
}
.fa-thermometer-2:before,
.fa-thermometer-half:before {
content: "\f2c9";
}
.fa-thermometer-1:before,
.fa-thermometer-quarter:before {
content: "\f2ca";
}
.fa-thermometer-0:before,
.fa-thermometer-empty:before {
content: "\f2cb";
}
.fa-shower:before {
content: "\f2cc";
}
.fa-bathtub:before,
.fa-s15:before,
.fa-bath:before {
content: "\f2cd";
}
.fa-podcast:before {
content: "\f2ce";
}
.fa-window-maximize:before {
content: "\f2d0";
}
.fa-window-minimize:before {
content: "\f2d1";
}
.fa-window-restore:before {
content: "\f2d2";
}
.fa-times-rectangle:before,
.fa-window-close:before {
content: "\f2d3";
}
.fa-times-rectangle-o:before,
.fa-window-close-o:before {
content: "\f2d4";
}
.fa-bandcamp:before {
content: "\f2d5";
}
.fa-grav:before {
content: "\f2d6";
}
.fa-etsy:before {
content: "\f2d7";
}
.fa-imdb:before {
content: "\f2d8";
}
.fa-ravelry:before {
content: "\f2d9";
}
.fa-eercast:before {
content: "\f2da";
}
.fa-microchip:before {
content: "\f2db";
}
.fa-snowflake-o:before {
content: "\f2dc";
}
.fa-superpowers:before {
content: "\f2dd";
}
.fa-wpexplorer:before {
content: "\f2de";
}
.fa-meetup:before {
content: "\f2e0";
}
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
border: 0;
}
.sr-only-focusable:active,
.sr-only-focusable:focus {
position: static;
width: auto;
height: auto;
margin: 0;
overflow: visible;
clip: auto;
}
================================================
FILE: src/assets/styles/vendor/fullcalendar.scss
================================================
@use '../spec/settings/baseColors' as *;
.fc {
background-color: var(--c-bkg-card) !important;
border: 1px solid var(--c-border) !important;
color: var(--c-text-base) !important;
// All table elements within the calendar
table {
background-color: var(--c-bkg-card) !important;
border-color: var(--c-border) !important;
}
th {
text-align: center;
padding: 15px;
background-color: var(--c-bkg-card) !important;
color: var(--c-text-base) !important;
font-size: 12px;
text-transform: uppercase;
border-right-width: 0;
border-left-width: 0;
border-color: var(--c-border) !important;
}
td {
background-color: var(--c-bkg-card) !important;
border-color: var(--c-border) !important;
color: var(--c-text-base) !important;
}
button {
background-color: var(--c-bkg-card) !important;
background-image: none;
height: 37px;
padding: 0 15px;
color: var(--c-text-base) !important;
border-color: var(--c-border) !important;
&.fc-state-default {
border-color: var(--c-border) !important;
box-shadow: none;
background-color: var(--c-bkg-card) !important;
color: var(--c-text-base) !important;
}
&.fc-state-active {
box-shadow: none;
background-color: var(--c-primary) !important;
color: white !important;
border-color: var(--c-primary) !important;
}
&:hover {
background-color: color-mix(in srgb, var(--c-bkg-card) 85%, var(--c-border)) !important;
border-color: var(--c-border) !important;
color: var(--c-text-base) !important;
}
}
// Calendar header/toolbar
.fc-head {
background-color: var(--c-bkg-card) !important;
border-color: var(--c-border) !important;
}
.fc-head-container {
background-color: var(--c-bkg-card) !important;
border-color: var(--c-border) !important;
}
// Calendar body
.fc-body {
background-color: var(--c-bkg-card) !important;
border-color: var(--c-border) !important;
}
// Individual day cells
.fc-day {
background-color: var(--c-bkg-card) !important;
border-color: var(--c-border) !important;
&:hover {
background-color: color-mix(in srgb, var(--c-bkg-card) 90%, var(--c-border)) !important;
}
}
// Week cells
.fc-week {
background-color: var(--c-bkg-card) !important;
border-color: var(--c-border) !important;
}
// All grid elements
.fc-widget-header,
.fc-widget-content {
border-color: var(--c-border) !important;
background-color: var(--c-bkg-card) !important;
}
}
.fc-toolbar {
padding: 20px 20px 0;
background-color: var(--c-bkg-card) !important;
}
.fc-view,
.fc-view > table {
background-color: var(--c-bkg-card) !important;
border-color: var(--c-border) !important;
}
.fc-basic-view td.fc-day-number,
.fc-basic-view td.fc-week-number span {
padding: 7px 15px;
color: var(--c-text-base) !important;
}
.fc-unthemed {
background-color: var(--c-bkg-card) !important;
.fc-content,
.fc-divider,
.fc-popover,
.fc-row,
tbody,
td,
th,
thead {
border-color: var(--c-border) !important;
background-color: var(--c-bkg-card) !important;
}
.fc-today {
background-color: color-mix(in srgb, var(--c-primary) 10%, var(--c-bkg-card)) !important;
}
.fc-popover {
background: var(--c-bkg-card) !important;
color: var(--c-text-base) !important;
border-color: var(--c-border) !important;
}
// Other month dates (faded)
.fc-other-month {
.fc-day-number {
color: var(--c-text-muted) !important;
}
}
// Weekend styling
.fc-sun,
.fc-sat {
background-color: color-mix(in srgb, var(--c-bkg-card) 95%, var(--c-border)) !important;
}
}
.fc-basic-view {
background-color: var(--c-bkg-card) !important;
.fc-day-number {
color: var(--c-text-base) !important;
&.fc-today {
background-color: var(--c-primary) !important;
color: white !important;
display: inline-block;
float: right;
border-radius: 50%;
padding: 6px 8px;
line-height: 1;
margin: 4px 4px 0 0;
}
}
.fc-day-header {
background-color: var(--c-bkg-card) !important;
color: var(--c-text-base) !important;
border-color: var(--c-border) !important;
}
}
.fc-event-container {
.fc-event {
border-radius: 3px;
border: 0;
background-color: var(--c-primary) !important;
color: white !important;
font-size: 12px;
line-height: 2.5;
padding: 0 15px;
&:hover {
background-color: var(--c-primary-hover) !important;
opacity: 0.9;
}
}
.fc-day-grid-event {
margin: 1px 5px 5px;
}
}
// Comprehensive border fix for all calendar elements
.fc * {
border-color: var(--c-border) !important;
}
// Fix for any remaining border inconsistencies
.fc .fc-grid,
.fc .fc-grid table,
.fc .fc-grid tr,
.fc .fc-grid td,
.fc .fc-grid th {
border-color: var(--c-border) !important;
background-color: var(--c-bkg-card) !important;
}
================================================
FILE: src/assets/styles/vendor/index.scss
================================================
@use 'perfect-scrollbar/css/perfect-scrollbar' as *;
@use 'themify-icons' as *;
@use 'font-awesome' as *;
@use 'perfectScrollbar' as *;
@use 'sparkline' as *;
@use 'jquery.datatables' as *;
@use 'fullcalendar' as *;
@use 'datepicker' as *;
================================================
FILE: src/assets/styles/vendor/jquery.datatables.scss
================================================
@use '../spec/settings/baseColors' as *;
@use '../spec/settings/breakpoints' as *;
@use '../spec/tools/mixins/mediaQueriesRanges' as *;
table {
&.dataTable {
background: var(--c-bkg-card);
color: var(--c-text-base);
&.no-footer {
border-bottom: 1px solid var(--c-border);
margin-bottom: 20px;
}
thead th {
background: var(--c-bkg-card);
color: var(--c-text-base);
border-color: var(--c-border);
}
tbody td {
background: var(--c-bkg-card);
color: var(--c-text-base);
border-color: var(--c-border);
}
tbody tr:nth-child(even) td {
background: color-mix(in srgb, var(--c-bkg-card) 95%, var(--c-border));
}
tbody tr:hover td {
background: color-mix(in srgb, var(--c-bkg-card) 90%, var(--c-border)) !important;
}
}
}
.sorting_asc {
&:focus {
outline: none;
}
}
.dataTables_wrapper {
overflow: hidden;
padding-bottom: 5px;
.dataTables_length{
color: var(--c-text-base);
float: left;
@include to($breakpoint-sm) {
text-align: left;
}
select {
border: 1px solid var(--c-border);
border-radius: 2px;
box-shadow: none;
height: 35px;
font-size: 14px;
padding: 5px;
margin-left: 5px;
margin-right: 5px;
color: var(--c-text-base);
background: var(--c-bkg-card);
transition: all 0.2s ease-in;
}
}
.dataTables_filter {
color: var(--c-text-base);
float: right;
@include to($breakpoint-sm) {
text-align: left;
}
input {
border: 1px solid var(--c-border);
border-radius: 2px;
box-shadow: none;
height: 35px;
font-size: 14px;
margin-left: 15px;
padding: 5px;
color: var(--c-text-base);
background: var(--c-bkg-card);
transition: all 0.2s ease-in;
&::placeholder {
color: var(--c-text-muted);
}
}
}
.dataTables_info {
color: var(--c-text-base);
float: left;
}
.dataTables_processing {
color: var(--c-text-base);
}
.dataTables_paginate {
color: var(--c-text-base);
float: right;
.paginate_button {
color: var(--c-text-base) !important;
padding: 6px 12px;
border-radius: 2px;
margin-right: 10px;
transition: all 0.2s ease-in-out;
text-decoration: none;
background: var(--c-bkg-card);
border: 1px solid var(--c-border);
&.next,
&.previous,
&.first,
&.last {
border-radius: 2px;
text-decoration: none;
&:hover,
&:focus {
color: #fff !important;
background: var(--c-primary);
}
&.disabled {
opacity: 0.4;
pointer-events: none;
}
}
&:hover {
color: #fff !important;
background: var(--c-primary);
border-color: var(--c-primary);
}
&.current {
color: #fff !important;
background: var(--c-primary);
border-color: var(--c-primary);
&:hover {
color: #fff !important;
background: var(--c-primary);
}
}
}
}
.status {
width: 5px;
height: 5px;
}
}
================================================
FILE: src/assets/styles/vendor/perfectScrollbar.scss
================================================
.ps__rail-y {
right: 0 !important;
left: auto !important;
}
================================================
FILE: src/assets/styles/vendor/sparkline.scss
================================================
#jqstooltip {
width: auto !important;
height: auto !important;
padding: 5px 10px !important;
border-radius: 2px !important;
}
================================================
FILE: src/assets/styles/vendor/themify-icons.css
================================================
@font-face {
font-family: 'themify';
src:url('../static/fonts/icons/themify/themify.eot?-fvbane');
src:url('../static/fonts/icons/themify/themify.eot?#iefix-fvbane') format('embedded-opentype'),
url('../static/fonts/icons/themify/themify.woff?-fvbane') format('woff'),
url('../static/fonts/icons/themify/themify.ttf?-fvbane') format('truetype'),
url('../static/fonts/icons/themify/themify.svg?-fvbane#themify') format('svg');
font-weight: normal;
font-style: normal;
}
[class^="ti-"], [class*=" ti-"] {
font-family: 'themify';
speak: none;
font-style: normal;
font-weight: normal;
font-variant: normal;
text-transform: none;
line-height: 1;
/* Better Font Rendering =========== */
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.ti-wand:before {
content: "\e600";
}
.ti-volume:before {
content: "\e601";
}
.ti-user:before {
content: "\e602";
}
.ti-unlock:before {
content: "\e603";
}
.ti-unlink:before {
content: "\e604";
}
.ti-trash:before {
content: "\e605";
}
.ti-thought:before {
content: "\e606";
}
.ti-target:before {
content: "\e607";
}
.ti-tag:before {
content: "\e608";
}
.ti-tablet:before {
content: "\e609";
}
.ti-star:before {
content: "\e60a";
}
.ti-spray:before {
content: "\e60b";
}
.ti-signal:before {
content: "\e60c";
}
.ti-shopping-cart:before {
content: "\e60d";
}
.ti-shopping-cart-full:before {
content: "\e60e";
}
.ti-settings:before {
content: "\e60f";
}
.ti-search:before {
content: "\e610";
}
.ti-zoom-in:before {
content: "\e611";
}
.ti-zoom-out:before {
content: "\e612";
}
.ti-cut:before {
content: "\e613";
}
.ti-ruler:before {
content: "\e614";
}
.ti-ruler-pencil:before {
content: "\e615";
}
.ti-ruler-alt:before {
content: "\e616";
}
.ti-bookmark:before {
content: "\e617";
}
.ti-bookmark-alt:before {
content: "\e618";
}
.ti-reload:before {
content: "\e619";
}
.ti-plus:before {
content: "\e61a";
}
.ti-pin:before {
content: "\e61b";
}
.ti-pencil:before {
content: "\e61c";
}
.ti-pencil-alt:before {
content: "\e61d";
}
.ti-paint-roller:before {
content: "\e61e";
}
.ti-paint-bucket:before {
content: "\e61f";
}
.ti-na:before {
content: "\e620";
}
.ti-mobile:before {
content: "\e621";
}
.ti-minus:before {
content: "\e622";
}
.ti-medall:before {
content: "\e623";
}
.ti-medall-alt:before {
content: "\e624";
}
.ti-marker:before {
content: "\e625";
}
.ti-marker-alt:before {
content: "\e626";
}
.ti-arrow-up:before {
content: "\e627";
}
.ti-arrow-right:before {
content: "\e628";
}
.ti-arrow-left:before {
content: "\e629";
}
.ti-arrow-down:before {
content: "\e62a";
}
.ti-lock:before {
content: "\e62b";
}
.ti-location-arrow:before {
content: "\e62c";
}
.ti-link:before {
content: "\e62d";
}
.ti-layout:before {
content: "\e62e";
}
.ti-layers:before {
content: "\e62f";
}
.ti-layers-alt:before {
content: "\e630";
}
.ti-key:before {
content: "\e631";
}
.ti-import:before {
content: "\e632";
}
.ti-image:before {
content: "\e633";
}
.ti-heart:before {
content: "\e634";
}
.ti-heart-broken:before {
content: "\e635";
}
.ti-hand-stop:before {
content: "\e636";
}
.ti-hand-open:before {
content: "\e637";
}
.ti-hand-drag:before {
content: "\e638";
}
.ti-folder:before {
content: "\e639";
}
.ti-flag:before {
content: "\e63a";
}
.ti-flag-alt:before {
content: "\e63b";
}
.ti-flag-alt-2:before {
content: "\e63c";
}
.ti-eye:before {
content: "\e63d";
}
.ti-export:before {
content: "\e63e";
}
.ti-exchange-vertical:before {
content: "\e63f";
}
.ti-desktop:before {
content: "\e640";
}
.ti-cup:before {
content: "\e641";
}
.ti-crown:before {
content: "\e642";
}
.ti-comments:before {
content: "\e643";
}
.ti-comment:before {
content: "\e644";
}
.ti-comment-alt:before {
content: "\e645";
}
.ti-close:before {
content: "\e646";
}
.ti-clip:before {
content: "\e647";
}
.ti-angle-up:before {
content: "\e648";
}
.ti-angle-right:before {
content: "\e649";
}
.ti-angle-left:before {
content: "\e64a";
}
.ti-angle-down:before {
content: "\e64b";
}
.ti-check:before {
content: "\e64c";
}
.ti-check-box:before {
content: "\e64d";
}
.ti-camera:before {
content: "\e64e";
}
.ti-announcement:before {
content: "\e64f";
}
.ti-brush:before {
content: "\e650";
}
.ti-briefcase:before {
content: "\e651";
}
.ti-bolt:before {
content: "\e652";
}
.ti-bolt-alt:before {
content: "\e653";
}
.ti-blackboard:before {
content: "\e654";
}
.ti-bag:before {
content: "\e655";
}
.ti-move:before {
content: "\e656";
}
.ti-arrows-vertical:before {
content: "\e657";
}
.ti-arrows-horizontal:before {
content: "\e658";
}
.ti-fullscreen:before {
content: "\e659";
}
.ti-arrow-top-right:before {
content: "\e65a";
}
.ti-arrow-top-left:before {
content: "\e65b";
}
.ti-arrow-circle-up:before {
content: "\e65c";
}
.ti-arrow-circle-right:before {
content: "\e65d";
}
.ti-arrow-circle-left:before {
content: "\e65e";
}
.ti-arrow-circle-down:before {
content: "\e65f";
}
.ti-angle-double-up:before {
content: "\e660";
}
.ti-angle-double-right:before {
content: "\e661";
}
.ti-angle-double-left:before {
content: "\e662";
}
.ti-angle-double-down:before {
content: "\e663";
}
.ti-zip:before {
content: "\e664";
}
.ti-world:before {
content: "\e665";
}
.ti-wheelchair:before {
content: "\e666";
}
.ti-view-list:before {
content: "\e667";
}
.ti-view-list-alt:before {
content: "\e668";
}
.ti-view-grid:before {
content: "\e669";
}
.ti-uppercase:before {
content: "\e66a";
}
.ti-upload:before {
content: "\e66b";
}
.ti-underline:before {
content: "\e66c";
}
.ti-truck:before {
content: "\e66d";
}
.ti-timer:before {
content: "\e66e";
}
.ti-ticket:before {
content: "\e66f";
}
.ti-thumb-up:before {
content: "\e670";
}
.ti-thumb-down:before {
content: "\e671";
}
.ti-text:before {
content: "\e672";
}
.ti-stats-up:before {
content: "\e673";
}
.ti-stats-down:before {
content: "\e674";
}
.ti-split-v:before {
content: "\e675";
}
.ti-split-h:before {
content: "\e676";
}
.ti-smallcap:before {
content: "\e677";
}
.ti-shine:before {
content: "\e678";
}
.ti-shift-right:before {
content: "\e679";
}
.ti-shift-left:before {
content: "\e67a";
}
.ti-shield:before {
content: "\e67b";
}
.ti-notepad:before {
content: "\e67c";
}
.ti-server:before {
content: "\e67d";
}
.ti-quote-right:before {
content: "\e67e";
}
.ti-quote-left:before {
content: "\e67f";
}
.ti-pulse:before {
content: "\e680";
}
.ti-printer:before {
content: "\e681";
}
.ti-power-off:before {
content: "\e682";
}
.ti-plug:before {
content: "\e683";
}
.ti-pie-chart:before {
content: "\e684";
}
.ti-paragraph:before {
content: "\e685";
}
.ti-panel:before {
content: "\e686";
}
.ti-package:before {
content: "\e687";
}
.ti-music:before {
content: "\e688";
}
.ti-music-alt:before {
content: "\e689";
}
.ti-mouse:before {
content: "\e68a";
}
.ti-mouse-alt:before {
content: "\e68b";
}
.ti-money:before {
content: "\e68c";
}
.ti-microphone:before {
content: "\e68d";
}
.ti-menu:before {
content: "\e68e";
}
.ti-menu-alt:before {
content: "\e68f";
}
.ti-map:before {
content: "\e690";
}
.ti-map-alt:before {
content: "\e691";
}
.ti-loop:before {
content: "\e692";
}
.ti-location-pin:before {
content: "\e693";
}
.ti-list:before {
content: "\e694";
}
.ti-light-bulb:before {
content: "\e695";
}
.ti-Italic:before {
content: "\e696";
}
.ti-info:before {
content: "\e697";
}
.ti-infinite:before {
content: "\e698";
}
.ti-id-badge:before {
content: "\e699";
}
.ti-hummer:before {
content: "\e69a";
}
.ti-home:before {
content: "\e69b";
}
.ti-help:before {
content: "\e69c";
}
.ti-headphone:before {
content: "\e69d";
}
.ti-harddrives:before {
content: "\e69e";
}
.ti-harddrive:before {
content: "\e69f";
}
.ti-gift:before {
content: "\e6a0";
}
.ti-game:before {
content: "\e6a1";
}
.ti-filter:before {
content: "\e6a2";
}
.ti-files:before {
content: "\e6a3";
}
.ti-file:before {
content: "\e6a4";
}
.ti-eraser:before {
content: "\e6a5";
}
.ti-envelope:before {
content: "\e6a6";
}
.ti-download:before {
content: "\e6a7";
}
.ti-direction:before {
content: "\e6a8";
}
.ti-direction-alt:before {
content: "\e6a9";
}
.ti-dashboard:before {
content: "\e6aa";
}
.ti-control-stop:before {
content: "\e6ab";
}
.ti-control-shuffle:before {
content: "\e6ac";
}
.ti-control-play:before {
content: "\e6ad";
}
.ti-control-pause:before {
content: "\e6ae";
}
.ti-control-forward:before {
content: "\e6af";
}
.ti-control-backward:before {
content: "\e6b0";
}
.ti-cloud:before {
content: "\e6b1";
}
.ti-cloud-up:before {
content: "\e6b2";
}
.ti-cloud-down:before {
content: "\e6b3";
}
.ti-clipboard:before {
content: "\e6b4";
}
.ti-car:before {
content: "\e6b5";
}
.ti-calendar:before {
content: "\e6b6";
}
.ti-book:before {
content: "\e6b7";
}
.ti-bell:before {
content: "\e6b8";
}
.ti-basketball:before {
content: "\e6b9";
}
.ti-bar-chart:before {
content: "\e6ba";
}
.ti-bar-chart-alt:before {
content: "\e6bb";
}
.ti-back-right:before {
content: "\e6bc";
}
.ti-back-left:before {
content: "\e6bd";
}
.ti-arrows-corner:before {
content: "\e6be";
}
.ti-archive:before {
content: "\e6bf";
}
.ti-anchor:before {
content: "\e6c0";
}
.ti-align-right:before {
content: "\e6c1";
}
.ti-align-left:before {
content: "\e6c2";
}
.ti-align-justify:before {
content: "\e6c3";
}
.ti-align-center:before {
content: "\e6c4";
}
.ti-alert:before {
content: "\e6c5";
}
.ti-alarm-clock:before {
content: "\e6c6";
}
.ti-agenda:before {
content: "\e6c7";
}
.ti-write:before {
content: "\e6c8";
}
.ti-window:before {
content: "\e6c9";
}
.ti-widgetized:before {
content: "\e6ca";
}
.ti-widget:before {
content: "\e6cb";
}
.ti-widget-alt:before {
content: "\e6cc";
}
.ti-wallet:before {
content: "\e6cd";
}
.ti-video-clapper:before {
content: "\e6ce";
}
.ti-video-camera:before {
content: "\e6cf";
}
.ti-vector:before {
content: "\e6d0";
}
.ti-themify-logo:before {
content: "\e6d1";
}
.ti-themify-favicon:before {
content: "\e6d2";
}
.ti-themify-favicon-alt:before {
content: "\e6d3";
}
.ti-support:before {
content: "\e6d4";
}
.ti-stamp:before {
content: "\e6d5";
}
.ti-split-v-alt:before {
content: "\e6d6";
}
.ti-slice:before {
content: "\e6d7";
}
.ti-shortcode:before {
content: "\e6d8";
}
.ti-shift-right-alt:before {
content: "\e6d9";
}
.ti-shift-left-alt:before {
content: "\e6da";
}
.ti-ruler-alt-2:before {
content: "\e6db";
}
.ti-receipt:before {
content: "\e6dc";
}
.ti-pin2:before {
content: "\e6dd";
}
.ti-pin-alt:before {
content: "\e6de";
}
.ti-pencil-alt2:before {
content: "\e6df";
}
.ti-palette:before {
content: "\e6e0";
}
.ti-more:before {
content: "\e6e1";
}
.ti-more-alt:before {
content: "\e6e2";
}
.ti-microphone-alt:before {
content: "\e6e3";
}
.ti-magnet:before {
content: "\e6e4";
}
.ti-line-double:before {
content: "\e6e5";
}
.ti-line-dotted:before {
content: "\e6e6";
}
.ti-line-dashed:before {
content: "\e6e7";
}
.ti-layout-width-full:before {
content: "\e6e8";
}
.ti-layout-width-default:before {
content: "\e6e9";
}
.ti-layout-width-default-alt:before {
content: "\e6ea";
}
.ti-layout-tab:before {
content: "\e6eb";
}
.ti-layout-tab-window:before {
content: "\e6ec";
}
.ti-layout-tab-v:before {
content: "\e6ed";
}
.ti-layout-tab-min:before {
content: "\e6ee";
}
.ti-layout-slider:before {
content: "\e6ef";
}
.ti-layout-slider-alt:before {
content: "\e6f0";
}
.ti-layout-sidebar-right:before {
content: "\e6f1";
}
.ti-layout-sidebar-none:before {
content: "\e6f2";
}
.ti-layout-sidebar-left:before {
content: "\e6f3";
}
.ti-layout-placeholder:before {
content: "\e6f4";
}
.ti-layout-menu:before {
content: "\e6f5";
}
.ti-layout-menu-v:before {
content: "\e6f6";
}
.ti-layout-menu-separated:before {
content: "\e6f7";
}
.ti-layout-menu-full:before {
content: "\e6f8";
}
.ti-layout-media-right-alt:before {
content: "\e6f9";
}
.ti-layout-media-right:before {
content: "\e6fa";
}
.ti-layout-media-overlay:before {
content: "\e6fb";
}
.ti-layout-media-overlay-alt:before {
content: "\e6fc";
}
.ti-layout-media-overlay-alt-2:before {
content: "\e6fd";
}
.ti-layout-media-left-alt:before {
content: "\e6fe";
}
.ti-layout-media-left:before {
content: "\e6ff";
}
.ti-layout-media-center-alt:before {
content: "\e700";
}
.ti-layout-media-center:before {
content: "\e701";
}
.ti-layout-list-thumb:before {
content: "\e702";
}
.ti-layout-list-thumb-alt:before {
content: "\e703";
}
.ti-layout-list-post:before {
content: "\e704";
}
.ti-layout-list-large-image:before {
content: "\e705";
}
.ti-layout-line-solid:before {
content: "\e706";
}
.ti-layout-grid4:before {
content: "\e707";
}
.ti-layout-grid3:before {
content: "\e708";
}
.ti-layout-grid2:before {
content: "\e709";
}
.ti-layout-grid2-thumb:before {
content: "\e70a";
}
.ti-layout-cta-right:before {
content: "\e70b";
}
.ti-layout-cta-left:before {
content: "\e70c";
}
.ti-layout-cta-center:before {
content: "\e70d";
}
.ti-layout-cta-btn-right:before {
content: "\e70e";
}
.ti-layout-cta-btn-left:before {
content: "\e70f";
}
.ti-layout-column4:before {
content: "\e710";
}
.ti-layout-column3:before {
content: "\e711";
}
.ti-layout-column2:before {
content: "\e712";
}
.ti-layout-accordion-separated:before {
content: "\e713";
}
.ti-layout-accordion-merged:before {
content: "\e714";
}
.ti-layout-accordion-list:before {
content: "\e715";
}
.ti-ink-pen:before {
content: "\e716";
}
.ti-info-alt:before {
content: "\e717";
}
.ti-help-alt:before {
content: "\e718";
}
.ti-headphone-alt:before {
content: "\e719";
}
.ti-hand-point-up:before {
content: "\e71a";
}
.ti-hand-point-right:before {
content: "\e71b";
}
.ti-hand-point-left:before {
content: "\e71c";
}
.ti-hand-point-down:before {
content: "\e71d";
}
.ti-gallery:before {
content: "\e71e";
}
.ti-face-smile:before {
content: "\e71f";
}
.ti-face-sad:before {
content: "\e720";
}
.ti-credit-card:before {
content: "\e721";
}
.ti-control-skip-forward:before {
content: "\e722";
}
.ti-control-skip-backward:before {
content: "\e723";
}
.ti-control-record:before {
content: "\e724";
}
.ti-control-eject:before {
content: "\e725";
}
.ti-comments-smiley:before {
content: "\e726";
}
.ti-brush-alt:before {
content: "\e727";
}
.ti-youtube:before {
content: "\e728";
}
.ti-vimeo:before {
content: "\e729";
}
.ti-twitter:before {
content: "\e72a";
}
.ti-time:before {
content: "\e72b";
}
.ti-tumblr:before {
content: "\e72c";
}
.ti-skype:before {
content: "\e72d";
}
.ti-share:before {
content: "\e72e";
}
.ti-share-alt:before {
content: "\e72f";
}
.ti-rocket:before {
content: "\e730";
}
.ti-pinterest:before {
content: "\e731";
}
.ti-new-window:before {
content: "\e732";
}
.ti-microsoft:before {
content: "\e733";
}
.ti-list-ol:before {
content: "\e734";
}
.ti-linkedin:before {
content: "\e735";
}
.ti-layout-sidebar-2:before {
content: "\e736";
}
.ti-layout-grid4-alt:before {
content: "\e737";
}
.ti-layout-grid3-alt:before {
content: "\e738";
}
.ti-layout-grid2-alt:before {
content: "\e739";
}
.ti-layout-column4-alt:before {
content: "\e73a";
}
.ti-layout-column3-alt:before {
content: "\e73b";
}
.ti-layout-column2-alt:before {
content: "\e73c";
}
.ti-instagram:before {
content: "\e73d";
}
.ti-google:before {
content: "\e73e";
}
.ti-github:before {
content: "\e73f";
}
.ti-flickr:before {
content: "\e740";
}
.ti-facebook:before {
content: "\e741";
}
.ti-dropbox:before {
content: "\e742";
}
.ti-dribbble:before {
content: "\e743";
}
.ti-apple:before {
content: "\e744";
}
.ti-android:before {
content: "\e745";
}
.ti-save:before {
content: "\e746";
}
.ti-save-alt:before {
content: "\e747";
}
.ti-yahoo:before {
content: "\e748";
}
.ti-wordpress:before {
content: "\e749";
}
.ti-vimeo-alt:before {
content: "\e74a";
}
.ti-twitter-alt:before {
content: "\e74b";
}
.ti-tumblr-alt:before {
content: "\e74c";
}
.ti-trello:before {
content: "\e74d";
}
.ti-stack-overflow:before {
content: "\e74e";
}
.ti-soundcloud:before {
content: "\e74f";
}
.ti-sharethis:before {
content: "\e750";
}
.ti-sharethis-alt:before {
content: "\e751";
}
.ti-reddit:before {
content: "\e752";
}
.ti-pinterest-alt:before {
content: "\e753";
}
.ti-microsoft-alt:before {
content: "\e754";
}
.ti-linux:before {
content: "\e755";
}
.ti-jsfiddle:before {
content: "\e756";
}
.ti-joomla:before {
content: "\e757";
}
.ti-html5:before {
content: "\e758";
}
.ti-flickr-alt:before {
content: "\e759";
}
.ti-email:before {
content: "\e75a";
}
.ti-drupal:before {
content: "\e75b";
}
.ti-dropbox-alt:before {
content: "\e75c";
}
.ti-css3:before {
content: "\e75d";
}
.ti-rss:before {
content: "\e75e";
}
.ti-rss-alt:before {
content: "\e75f";
}
================================================
FILE: src/basic-table.html
================================================
Basic Table
Basic Tables
Simple Table
Using the most basic table markup, here's how .table-based tables look in Bootstrap. All table styles are inherited in Bootstrap 5 , meaning any nested tables will be styled in the same manner as the parent.
#
First Name
Last Name
Username
1
Mark
Otto
@mdo
2
Jacob
Thornton
@fat
3
Larry
the Bird
@twitter
Table head options
Similar to tables and dark tables, use the modifier classes .table-light or .table-dark to make <thead>s appear light or dark gray.
#
First Name
Last Name
Username
1
Mark
Otto
@mdo
2
Jacob
Thornton
@fat
3
Larry
the Bird
@twitter
Striped rows
Use .table-striped to add zebra-striping to any table row within the <tbody>.
#
First Name
Last Name
Username
1
Mark
Otto
@mdo
2
Jacob
Thornton
@fat
3
Larry
the Bird
@twitter
Bordered table
Add .table-bordered for borders on all sides of the table and cells.
#
First Name
Last Name
Username
1
Mark
Otto
@mdo
2
Mark
Otto
@TwBootstrap
3
Jacob
Thornton
@fat
4
Larry the Bird
@twitter
Hoverable rows
Add .table-hover to enable a hover state on table rows within a <tbody>.
#
First Name
Last Name
Username
1
Mark
Otto
@mdo
2
Jacob
Thornton
@fat
3
Larry the Bird
@twitter
Copyright © 2026 Designed by Colorlib . All rights reserved.
================================================
FILE: src/blank.html
================================================
Blank
Copyright © 2026 Designed by Colorlib . All rights reserved.
================================================
FILE: src/buttons.html
================================================
Copyright © 2026 Designed by Colorlib . All rights reserved.
================================================
FILE: src/calendar.html
================================================
Calendar
Copyright © 2026 Designed by Colorlib . All rights reserved.
================================================
FILE: src/charts.html
================================================
Charts
Copyright © 2026 Designed by Colorlib . All rights reserved.
================================================
FILE: src/chat.html
================================================
Chat
Copyright © 2026 Designed by Colorlib . All rights reserved.
================================================
FILE: src/compose.html
================================================
Compose
Copyright © 2026 Designed by Colorlib . All rights reserved.
================================================
FILE: src/datatable.html
================================================
Data Tables
Data Tables
Bootstrap Data Table
Name
Position
Office
Age
Start date
Salary
Name
Position
Office
Age
Start date
Salary
Tiger Nixon
System Architect
Edinburgh
61
2011/04/25
$320,800
Garrett Winters
Accountant
Tokyo
63
2011/07/25
$170,750
Ashton Cox
Junior Technical Author
San Francisco
66
2009/01/12
$86,000
Cedric Kelly
Senior Javascript Developer
Edinburgh
22
2012/03/29
$433,060
Airi Satou
Accountant
Tokyo
33
2008/11/28
$162,700
Brielle Williamson
Integration Specialist
New York
61
2012/12/02
$372,000
Herrod Chandler
Sales Assistant
San Francisco
59
2012/08/06
$137,500
Rhona Davidson
Integration Specialist
Tokyo
55
2010/10/14
$327,900
Colleen Hurst
Javascript Developer
San Francisco
39
2009/09/15
$205,500
Sonya Frost
Software Engineer
Edinburgh
23
2008/12/13
$103,600
Jena Gaines
Office Manager
London
30
2008/12/19
$90,560
Quinn Flynn
Support Lead
Edinburgh
22
2013/03/03
$342,000
Charde Marshall
Regional Director
San Francisco
36
2008/10/16
$470,600
Haley Kennedy
Senior Marketing Designer
London
43
2012/12/18
$313,500
Tatyana Fitzpatrick
Regional Director
London
19
2010/03/17
$385,750
Michael Silva
Marketing Designer
London
66
2012/11/27
$198,500
Paul Byrd
Chief Financial Officer (CFO)
New York
64
2010/06/09
$725,000
Gloria Little
Systems Administrator
New York
59
2009/04/10
$237,500
Bradley Greer
Software Engineer
London
41
2012/10/13
$132,000
Dai Rios
Personnel Lead
Edinburgh
35
2012/09/26
$217,500
Jenette Caldwell
Development Lead
New York
30
2011/09/03
$345,000
Yuri Berry
Chief Marketing Officer (CMO)
New York
40
2009/06/25
$675,000
Caesar Vance
Pre-Sales Support
New York
21
2011/12/12
$106,450
Doris Wilder
Sales Assistant
Sidney
23
2010/09/20
$85,600
Angelica Ramos
Chief Executive Officer (CEO)
London
47
2009/10/09
$1,200,000
Gavin Joyce
Developer
Edinburgh
42
2010/12/22
$92,575
Jennifer Chang
Regional Director
Singapore
28
2010/11/14
$357,650
Brenden Wagner
Software Engineer
San Francisco
28
2011/06/07
$206,850
Fiona Green
Chief Operating Officer (COO)
San Francisco
48
2010/03/11
$850,000
Shou Itou
Regional Marketing
Tokyo
20
2011/08/14
$163,000
Michelle House
Integration Specialist
Sidney
37
2011/06/02
$95,400
Suki Burks
Developer
London
53
2009/10/22
$114,500
Prescott Bartlett
Technical Author
London
27
2011/05/07
$145,000
Gavin Cortez
Team Leader
San Francisco
22
2008/10/26
$235,500
Martena Mccray
Post-Sales support
Edinburgh
46
2011/03/09
$324,050
Unity Butler
Marketing Designer
San Francisco
47
2009/12/09
$85,675
Howard Hatfield
Office Manager
San Francisco
51
2008/12/16
$164,500
Hope Fuentes
Secretary
San Francisco
41
2010/02/12
$109,850
Vivian Harrell
Financial Controller
San Francisco
62
2009/02/14
$452,500
Timothy Mooney
Office Manager
London
37
2008/12/11
$136,200
Jackson Bradshaw
Director
New York
65
2008/09/26
$645,750
Olivia Liang
Support Engineer
Singapore
64
2011/02/03
$234,500
Bruno Nash
Software Engineer
London
38
2011/05/03
$163,500
Sakura Yamamoto
Support Engineer
Tokyo
37
2009/08/19
$139,575
Thor Walton
Developer
New York
61
2013/08/11
$98,540
Finn Camacho
Support Engineer
San Francisco
47
2009/07/07
$87,500
Serge Baldwin
Data Coordinator
Singapore
64
2012/04/09
$138,575
Zenaida Frank
Software Engineer
New York
63
2010/01/04
$125,250
Zorita Serrano
Software Engineer
San Francisco
56
2012/06/01
$115,000
Jennifer Acosta
Junior Javascript Developer
Edinburgh
43
2013/02/01
$75,650
Cara Stevens
Sales Assistant
New York
46
2011/12/06
$145,600
Hermione Butler
Regional Director
London
47
2011/03/21
$356,250
Lael Greer
Systems Administrator
London
21
2009/02/27
$103,500
Jonas Alexander
Developer
San Francisco
30
2010/07/14
$86,500
Shad Decker
Regional Director
Edinburgh
51
2008/11/13
$183,000
Michael Bruce
Javascript Developer
Singapore
29
2011/06/27
$183,000
Donna Snider
Customer Support
New York
27
2011/01/25
$112,000
Copyright © 2026 Designed by Colorlib . All rights reserved.
================================================
FILE: src/email.html
================================================
Email
Copyright © 2026 Designed by Colorlib . All rights reserved.
================================================
FILE: src/forms.html
================================================
Forms
Copyright © 2026 Designed by Colorlib . All rights reserved.
================================================
FILE: src/google-maps.html
================================================
Google Maps
Copyright © 2026 Designed by Colorlib . All rights reserved.
================================================
FILE: src/index.html
================================================
Dashboard
100k
Visitors From USA
50%
1M
Visitors From Europe
80%
450k
Visitors From Australia
40%
1B
Visitors From India
90%
Monthly Stats
54%
Sales Growth
$185K
Dec Sales
60%
Profit Growth
$72K
Dec Profit
Sales Report
Name
Status
Date
Price
Item #1 Name
Unavailable
Nov 18
$12
Item #2 Name
New
Nov 19
$34
Item #3 Name
New
Nov 20
-$45
Item #4 Name
Unavailable
Nov 21
$65
Item #5 Name
Used
Nov 22
$78
Item #6 Name
Used
Nov 23
-$88
Item #7 Name
Old
Nov 22
$56
Weather
MON
32°F
TUE
30°F
WED
28°F
THR
32°F
FRI
24°F
SAT
28°F
SUN
32°F
Quick Chat
10:00 AM
Lorem Ipsum is simply dummy text of
10:00 AM
the printing and typesetting industry.
10:00 AM
Lorem Ipsum has been the industry's
Copyright © 2026 Designed by Colorlib . All rights reserved.
================================================
FILE: src/signin.html
================================================
Sign In
================================================
FILE: src/signup.html
================================================
Sign Up
================================================
FILE: src/ui.html
================================================
UI Elements
UI Elements
Alerts
This is a primary alert—check it out!
This is a secondary alert—check it out!
This is a success alert—check it out!
This is a danger alert—check it out!
This is a warning alert—check it out!
This is a info alert—check it out!
This is a light alert—check it out!
This is a dark alert—check it out!
Buttons
Primary
Secondary
Success
Danger
Warning
Info
Light
Dark
Primary
Secondary
Success
Danger
Warning
Info
Light
Dark
Popover
Click to toggle popover
Progress
100k
Visitors From USA
50%
1M
Visitors From Europe
80%
450k
Visitors From Australia
40%
1B
Visitors From India
90%
Tootips
Tooltip on top
Tooltip on right
Tooltip on bottom
Tooltip on left
Button Sizes
Large button
Large button
Default button
Default button
Small button
Small button
Badges
Example heading New
Example heading 2
Example heading Success
Example heading Danger
Example heading Warning
Example heading Info
Example heading Light
Example heading Dark
Accordion
This is the first item's accordion body. It is shown by default, until the collapse plugin adds the appropriate classes.
This is the second item's accordion body. It is hidden by default, until the collapse plugin adds the appropriate classes.
Cards
Card title
Card subtitle
Some quick example text to build on the card title and make up the bulk of the card's content.
Card link
Another link
List Groups
An item
A second item
A third item
A fourth item
And a fifth one
Spinners
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Copyright © 2026 Designed by Colorlib . All rights reserved.
================================================
FILE: src/vector-maps.html
================================================
Vector Maps
Copyright © 2026 Designed by Colorlib . All rights reserved.
================================================
FILE: tests/setup.js
================================================
/**
* Vitest Test Setup
* Configures the test environment
*/
// Set up DOM environment
if (typeof document !== 'undefined') {
// Set default document structure
document.body.innerHTML = `
`;
}
// Mock localStorage
const localStorageMock = (() => {
let store = {};
return {
getItem: (key) => store[key] || null,
setItem: (key, value) => {
store[key] = String(value);
},
removeItem: (key) => {
delete store[key];
},
clear: () => {
store = {};
},
get length() {
return Object.keys(store).length;
},
key: (index) => Object.keys(store)[index] || null
};
})();
Object.defineProperty(window, 'localStorage', {
value: localStorageMock
});
// Mock matchMedia
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: (query) => ({
matches: false,
media: query,
onchange: null,
addListener: () => {},
removeListener: () => {},
addEventListener: () => {},
removeEventListener: () => {},
dispatchEvent: () => {}
})
});
// Clear localStorage before each test
beforeEach(() => {
localStorage.clear();
document.documentElement.removeAttribute('data-theme');
});
================================================
FILE: tests/utils/dom.test.js
================================================
/**
* DOM Utilities Tests
*/
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { DOM } from '../../src/assets/scripts/utils/dom.js';
describe('DOM Utilities', () => {
beforeEach(() => {
document.body.innerHTML = `
`;
});
describe('select()', () => {
it('selects a single element by selector', () => {
const btn = DOM.select('#btn');
expect(btn).not.toBeNull();
expect(btn.textContent).toBe('Click me');
});
it('returns null for non-existent elements', () => {
const el = DOM.select('.non-existent');
expect(el).toBeNull();
});
it('supports context parameter', () => {
const list = DOM.select('.list');
const item = DOM.select('.item', list);
expect(item.textContent).toBe('Item 1');
});
});
describe('selectAll()', () => {
it('selects all matching elements', () => {
const items = DOM.selectAll('.item');
expect(items).toHaveLength(3);
});
it('returns an array', () => {
const items = DOM.selectAll('.item');
expect(Array.isArray(items)).toBe(true);
});
it('returns empty array for no matches', () => {
const items = DOM.selectAll('.non-existent');
expect(items).toHaveLength(0);
});
});
describe('exists()', () => {
it('returns true for existing elements', () => {
expect(DOM.exists('#btn')).toBe(true);
});
it('returns false for non-existent elements', () => {
expect(DOM.exists('.non-existent')).toBe(false);
});
});
describe('on() / off()', () => {
it('adds event listener', () => {
const handler = vi.fn();
const btn = DOM.select('#btn');
DOM.on(btn, 'click', handler);
btn.click();
expect(handler).toHaveBeenCalledTimes(1);
});
it('supports selector string', () => {
const handler = vi.fn();
DOM.on('#btn', 'click', handler);
DOM.select('#btn').click();
expect(handler).toHaveBeenCalled();
});
it('removes event listener', () => {
const handler = vi.fn();
const btn = DOM.select('#btn');
DOM.on(btn, 'click', handler);
DOM.off(btn, 'click', handler);
btn.click();
expect(handler).not.toHaveBeenCalled();
});
});
describe('addClass() / removeClass() / toggleClass() / hasClass()', () => {
it('adds a class', () => {
const btn = DOM.select('#btn');
DOM.addClass(btn, 'active');
expect(btn.classList.contains('active')).toBe(true);
});
it('removes a class', () => {
const btn = DOM.select('#btn');
DOM.removeClass(btn, 'primary');
expect(btn.classList.contains('primary')).toBe(false);
});
it('toggles a class', () => {
const btn = DOM.select('#btn');
DOM.toggleClass(btn, 'active');
expect(btn.classList.contains('active')).toBe(true);
DOM.toggleClass(btn, 'active');
expect(btn.classList.contains('active')).toBe(false);
});
it('checks if element has class', () => {
expect(DOM.hasClass('#btn', 'primary')).toBe(true);
expect(DOM.hasClass('#btn', 'non-existent')).toBe(false);
});
it('supports selector strings', () => {
DOM.addClass('#btn', 'test-class');
expect(DOM.hasClass('#btn', 'test-class')).toBe(true);
});
});
describe('attr()', () => {
it('gets attribute value', () => {
const id = DOM.attr('#btn', 'id');
expect(id).toBe('btn');
});
it('sets attribute value', () => {
DOM.attr('#btn', 'title', 'My Button');
expect(DOM.select('#btn').getAttribute('title')).toBe('My Button');
});
it('returns null for non-existent element', () => {
expect(DOM.attr('.non-existent', 'id')).toBeNull();
});
});
describe('data()', () => {
it('gets data attribute value', () => {
const id = DOM.data('#input', 'id');
expect(id).toBe('123');
});
it('sets data attribute value', () => {
DOM.data('#input', 'value', '456');
expect(DOM.select('#input').getAttribute('data-value')).toBe('456');
});
});
describe('text() / html()', () => {
it('gets text content', () => {
expect(DOM.text('#btn')).toBe('Click me');
});
it('sets text content', () => {
DOM.text('#btn', 'New Text');
expect(DOM.select('#btn').textContent).toBe('New Text');
});
it('gets HTML content', () => {
const html = DOM.html('.container');
expect(html).toContain(' {
DOM.html('.panel', 'New ');
expect(DOM.select('.panel').innerHTML).toBe('New ');
});
});
describe('hide() / show() / toggle()', () => {
it('hides an element', () => {
DOM.hide('.panel');
expect(DOM.select('.panel').style.display).toBe('none');
});
it('shows an element', () => {
DOM.hide('.panel');
DOM.show('.panel');
expect(DOM.select('.panel').style.display).toBe('block');
});
it('shows with custom display value', () => {
DOM.hide('.panel');
DOM.show('.panel', 'flex');
expect(DOM.select('.panel').style.display).toBe('flex');
});
it('toggles visibility', () => {
const panel = DOM.select('.panel');
DOM.toggle(panel);
expect(panel.style.display).toBe('none');
DOM.toggle(panel);
expect(panel.style.display).toBe('block');
});
});
describe('dimensions()', () => {
it('returns dimensions object', () => {
const dims = DOM.dimensions('#btn');
expect(dims).toHaveProperty('width');
expect(dims).toHaveProperty('height');
expect(dims).toHaveProperty('top');
expect(dims).toHaveProperty('left');
});
it('returns null for non-existent element', () => {
expect(DOM.dimensions('.non-existent')).toBeNull();
});
});
describe('ready()', () => {
it('executes callback immediately when DOM is ready', () => {
const callback = vi.fn();
DOM.ready(callback);
expect(callback).toHaveBeenCalled();
});
});
describe('create()', () => {
it('creates an element with tag', () => {
const el = DOM.create('div');
expect(el.tagName).toBe('DIV');
});
it('creates element with attributes', () => {
const el = DOM.create('button', { class: 'btn', type: 'submit' });
expect(el.className).toBe('btn');
expect(el.type).toBe('submit');
});
it('creates element with text children', () => {
const el = DOM.create('span', {}, ['Hello']);
expect(el.textContent).toBe('Hello');
});
it('creates element with element children', () => {
const child = DOM.create('span', {}, ['Child']);
const parent = DOM.create('div', {}, [child]);
expect(parent.firstChild).toBe(child);
});
});
describe('closest()', () => {
it('finds closest ancestor', () => {
const item = DOM.select('.item');
const list = DOM.closest(item, '.list');
expect(list).not.toBeNull();
expect(list.classList.contains('list')).toBe(true);
});
it('returns null when no ancestor matches', () => {
const item = DOM.select('.item');
expect(DOM.closest(item, '.non-existent')).toBeNull();
});
});
});
================================================
FILE: tests/utils/logger.test.js
================================================
/**
* Logger Utility Tests
*/
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import Logger from '../../src/assets/scripts/utils/logger.js';
describe('Logger Utility', () => {
const originalEnv = process.env.NODE_ENV;
let consoleSpies;
beforeEach(() => {
consoleSpies = {
info: vi.spyOn(console, 'info').mockImplementation(() => {}),
warn: vi.spyOn(console, 'warn').mockImplementation(() => {}),
error: vi.spyOn(console, 'error').mockImplementation(() => {}),
debug: vi.spyOn(console, 'debug').mockImplementation(() => {}),
group: vi.spyOn(console, 'group').mockImplementation(() => {}),
groupEnd: vi.spyOn(console, 'groupEnd').mockImplementation(() => {}),
time: vi.spyOn(console, 'time').mockImplementation(() => {}),
timeEnd: vi.spyOn(console, 'timeEnd').mockImplementation(() => {}),
table: vi.spyOn(console, 'table').mockImplementation(() => {})
};
});
afterEach(() => {
process.env.NODE_ENV = originalEnv;
vi.restoreAllMocks();
});
describe('in development mode', () => {
beforeEach(() => {
process.env.NODE_ENV = 'development';
});
it('logs info messages', () => {
Logger.info('Test message', { data: 'test' });
expect(consoleSpies.info).toHaveBeenCalled();
});
it('logs warn messages', () => {
Logger.warn('Warning message');
expect(consoleSpies.warn).toHaveBeenCalled();
});
it('logs error messages', () => {
Logger.error('Error message', new Error('Test'));
expect(consoleSpies.error).toHaveBeenCalled();
});
it('logs debug messages', () => {
Logger.debug('Debug message');
expect(consoleSpies.debug).toHaveBeenCalled();
});
it('creates log groups', () => {
Logger.group('Test Group');
Logger.groupEnd();
expect(consoleSpies.group).toHaveBeenCalled();
expect(consoleSpies.groupEnd).toHaveBeenCalled();
});
it('logs timing', () => {
Logger.time('operation');
Logger.timeEnd('operation');
expect(consoleSpies.time).toHaveBeenCalled();
expect(consoleSpies.timeEnd).toHaveBeenCalled();
});
it('logs tables', () => {
Logger.table([{ a: 1 }, { a: 2 }]);
expect(consoleSpies.table).toHaveBeenCalled();
});
it('prefixes messages with [Adminator]', () => {
Logger.info('Test');
expect(consoleSpies.info).toHaveBeenCalledWith(
expect.stringContaining('[Adminator]'),
expect.anything()
);
});
});
describe('in production mode', () => {
beforeEach(() => {
process.env.NODE_ENV = 'production';
// Mock window.location for the isDev check
Object.defineProperty(window, 'location', {
value: { hostname: 'example.com' },
writable: true
});
});
it('does not log info messages', () => {
Logger.info('Test message');
expect(consoleSpies.info).not.toHaveBeenCalled();
});
it('does not log warn messages', () => {
Logger.warn('Warning message');
expect(consoleSpies.warn).not.toHaveBeenCalled();
});
it('does not log error messages', () => {
Logger.error('Error message');
expect(consoleSpies.error).not.toHaveBeenCalled();
});
});
});
================================================
FILE: tests/utils/theme.test.js
================================================
/**
* Theme Manager Tests
*/
import { describe, it, expect, beforeEach, vi } from 'vitest';
import Theme from '../../src/assets/scripts/utils/theme.js';
describe('Theme Manager', () => {
beforeEach(() => {
localStorage.clear();
document.documentElement.removeAttribute('data-theme');
});
describe('current()', () => {
it('returns "light" by default when no theme is stored', () => {
expect(Theme.current()).toBe('light');
});
it('returns stored theme from localStorage', () => {
localStorage.setItem('adminator-theme', 'dark');
expect(Theme.current()).toBe('dark');
});
it('returns "light" for invalid stored values', () => {
localStorage.setItem('adminator-theme', 'invalid');
expect(Theme.current()).toBe('light');
});
});
describe('apply()', () => {
it('sets data-theme attribute on document', () => {
Theme.apply('dark');
expect(document.documentElement.getAttribute('data-theme')).toBe('dark');
});
it('stores theme in localStorage', () => {
Theme.apply('dark');
expect(localStorage.getItem('adminator-theme')).toBe('dark');
});
it('dispatches adminator:themeChanged event', () => {
const handler = vi.fn();
window.addEventListener('adminator:themeChanged', handler);
Theme.apply('dark');
expect(handler).toHaveBeenCalled();
expect(handler.mock.calls[0][0].detail.theme).toBe('dark');
window.removeEventListener('adminator:themeChanged', handler);
});
it('falls back to "light" for invalid theme values', () => {
Theme.apply('invalid');
expect(document.documentElement.getAttribute('data-theme')).toBe('light');
});
it('returns true on success', () => {
expect(Theme.apply('dark')).toBe(true);
});
});
describe('toggle()', () => {
it('toggles from light to dark', () => {
Theme.apply('light');
const result = Theme.toggle();
expect(result).toBe('dark');
expect(Theme.current()).toBe('dark');
});
it('toggles from dark to light', () => {
Theme.apply('dark');
const result = Theme.toggle();
expect(result).toBe('light');
expect(Theme.current()).toBe('light');
});
});
describe('init()', () => {
it('uses stored preference if available', () => {
localStorage.setItem('adminator-theme', 'dark');
Theme.init();
expect(document.documentElement.getAttribute('data-theme')).toBe('dark');
});
it('returns the applied theme', () => {
localStorage.setItem('adminator-theme', 'dark');
expect(Theme.init()).toBe('dark');
});
});
describe('isDark() / isLight()', () => {
it('isDark returns true when theme is dark', () => {
Theme.apply('dark');
expect(Theme.isDark()).toBe(true);
expect(Theme.isLight()).toBe(false);
});
it('isLight returns true when theme is light', () => {
Theme.apply('light');
expect(Theme.isLight()).toBe(true);
expect(Theme.isDark()).toBe(false);
});
});
describe('getChartColors()', () => {
it('returns dark colors when theme is dark', () => {
Theme.apply('dark');
const colors = Theme.getChartColors();
expect(colors.textColor).toBe('#FFFFFF');
});
it('returns light colors when theme is light', () => {
Theme.apply('light');
const colors = Theme.getChartColors();
expect(colors.textColor).toBe('#212529');
});
});
});
================================================
FILE: vitest.config.js
================================================
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
environment: 'jsdom',
globals: true,
include: ['tests/**/*.test.js'],
coverage: {
provider: 'v8',
reporter: ['text', 'html'],
include: ['src/assets/scripts/utils/**/*.js'],
exclude: ['node_modules', 'dist', 'tests'],
},
setupFiles: ['./tests/setup.js'],
},
});
================================================
FILE: webpack/config.js
================================================
// ------------------
// @Table of Contents
// ------------------
/**
* + @Loading Dependencies
* + @Entry Point Setup
* + @Path Resolving
* + @Exporting Module
*/
// ---------------------
// @Loading Dependencies
// ---------------------
const
path = require('path'),
manifest = require('./manifest'),
devServer = require('./devServer'),
rules = require('./rules'),
plugins = require('./plugins');
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
const TerserPlugin = require("terser-webpack-plugin");
// ------------------
// @Entry Point Setup
// ------------------
const
entry = [
path.join(manifest.paths.src, 'assets', 'scripts', manifest.entries.js),
];
// ---------------
// @Path Resolving
// ---------------
const resolve = {
extensions: ['.tsx', '.ts', '.webpack-loader.js', '.web-loader.js', '.loader.js', '.js', '.jsx'],
modules: [
path.join(__dirname, '../node_modules'),
path.join(manifest.paths.src, ''),
],
alias: {
'@': path.join(manifest.paths.src),
'@/components': path.join(manifest.paths.src, 'assets', 'scripts', 'components'),
'@/utils': path.join(manifest.paths.src, 'assets', 'scripts', 'utils'),
'@/constants': path.join(manifest.paths.src, 'assets', 'scripts', 'constants'),
},
};
const optimization = {
minimize: manifest.MINIFY,
// Code splitting for better caching and smaller initial bundle
splitChunks: {
chunks: 'all',
maxInitialRequests: 25,
minSize: 20000,
cacheGroups: {
// Vendor chunks
chartjs: {
test: /[\\/]node_modules[\\/]chart\.js[\\/]/,
name: 'vendor-chartjs',
priority: 30,
},
fullcalendar: {
test: /[\\/]node_modules[\\/]@fullcalendar[\\/]/,
name: 'vendor-fullcalendar',
priority: 30,
},
bootstrap: {
test: /[\\/]node_modules[\\/]bootstrap[\\/]/,
name: 'vendor-bootstrap',
priority: 20,
},
// Other node_modules
vendors: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
priority: 10,
reuseExistingChunk: true,
},
// Common code shared between entry points
common: {
name: 'common',
minChunks: 2,
priority: 5,
reuseExistingChunk: true,
},
},
},
// Extract webpack runtime into separate chunk
runtimeChunk: 'single',
};
if (manifest.MINIFY) {
optimization.minimizer = [
new CssMinimizerPlugin(),
new TerserPlugin({
terserOptions: {
compress: {
drop_console: manifest.IS_PRODUCTION,
drop_debugger: manifest.IS_PRODUCTION,
},
output: {
comments: false,
},
},
extractComments: false,
}),
];
}
// -----------------
// @Exporting Module
// -----------------
module.exports = {
devtool: manifest.IS_PRODUCTION ? false : 'source-map',
context: path.join(manifest.paths.src, manifest.entries.js),
// watch: !manifest.IS_PRODUCTION,
entry,
mode: manifest.NODE_ENV,
// output: {
// path: manifest.paths.build,
// publicPath: '',
// filename: manifest.outputFiles.bundle,
// },
module: {
rules,
},
optimization,
resolve,
plugins,
devServer,
// Suppress Bootstrap SASS deprecation warnings (modern syntax)
ignoreWarnings: [
/Deprecation Warning/,
/node_modules\/bootstrap/,
/repetitive deprecation warnings omitted/,
/red\(\) is deprecated/,
/green\(\) is deprecated/,
/blue\(\) is deprecated/,
/Global built-in functions are deprecated/,
],
};
================================================
FILE: webpack/devServer.js
================================================
// ---------------------
// @Loading Dependencies
// ---------------------
const
manifest = require('./manifest');
// ------------------
// @DevServer Configs
// ------------------
/**
* [1] : To enable local network testing
*/
const devServer = {
static: {
directory: manifest.IS_PRODUCTION ? manifest.paths.build : manifest.paths.src,
watch: true,
},
historyApiFallback: true,
port: manifest.IS_PRODUCTION ? 3001 : 4000,
compress: manifest.IS_PRODUCTION,
client: {
overlay: true,
progress: !manifest.IS_PRODUCTION,
},
hot: !manifest.IS_PRODUCTION,
host: '0.0.0.0',
allowedHosts: 'all', // [1]
devMiddleware: {
stats: {
assets: true,
children: false,
chunks: false,
hash: false,
modules: false,
publicPath: false,
timings: true,
version: false,
warnings: true,
colors: true,
},
},
};
// -----------------
// @Exporting Module
// -----------------
module.exports = devServer;
================================================
FILE: webpack/manifest.js
================================================
// ------------------
// @Table of Contents
// ------------------
/**
* + @Loading Dependencies
* + @Environment Holders
* + @Utils
* + @App Paths
* + @Output Files Names
* + @Entries Files Names
* + @Exporting Module
*/
// ---------------------
// @Loading Dependencies
// ---------------------
const path = require('path');
// --------------------
// @Environment Holders
// --------------------
const
NODE_ENV = process.env.NODE_ENV || 'development',
IS_DEVELOPMENT = NODE_ENV === 'development',
IS_PRODUCTION = NODE_ENV === 'production',
MINIFY = process.env.MINIFY === 'true';
// ------
// @Utils
// ------
const
dir = src => path.join(__dirname, src);
// ----------
// @App Paths
// ----------
const
paths = {
src : dir('../src'),
build : dir('../dist'),
};
// -------------------
// @Output Files Names
// -------------------
const
outputFiles = {
bundle : 'bundle.js',
vendor : 'vendor.js',
css : 'style.css',
};
// --------------------
// @Entries Files Names
// --------------------
const
entries = {
js : 'index.js',
};
// -----------------
// @Exporting Module
// -----------------
module.exports = {
paths,
outputFiles,
entries,
NODE_ENV,
IS_DEVELOPMENT,
IS_PRODUCTION,
MINIFY,
};
================================================
FILE: webpack/plugins/caseSensitivePlugin.js
================================================
const
CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin');
module.exports = new CaseSensitivePathsPlugin();
================================================
FILE: webpack/plugins/copyPlugin.js
================================================
const
path = require('path'),
manifest = require('../manifest'),
CopyWebpackPlugin = require('copy-webpack-plugin');
module.exports = new CopyWebpackPlugin({
patterns: [
{
from : path.join(manifest.paths.src, 'assets/static'),
to : path.join(manifest.paths.build, 'assets/static'),
},
],
});
================================================
FILE: webpack/plugins/dashboardPlugin.js
================================================
const
DashboardPlugin = require('webpack-dashboard/plugin');
module.exports = new DashboardPlugin();
================================================
FILE: webpack/plugins/extractPlugin.js
================================================
const
manifest = require('../manifest'),
ExtractTextPlugin = require('mini-css-extract-plugin');
module.exports = new ExtractTextPlugin({
filename: manifest.outputFiles.css,
// allChunks: true,
});
================================================
FILE: webpack/plugins/htmlPlugin.js
================================================
const
path = require('path'),
manifest = require('../manifest'),
HtmlWebpackPlugin = require('html-webpack-plugin');
const titles = {
'index': 'Dashboard',
'blank': 'Blank',
'buttons': 'Buttons',
'calendar': 'Calendar',
'charts': 'Charts',
'chat': 'Chat',
'compose': 'Compose',
'datatable': 'Datatable',
'email': 'Email',
'forms': 'Forms',
'google-maps': 'Google Maps',
'signin': 'Signin',
'signup': 'Signup',
'ui': 'UI',
'vector-maps': 'Vector Maps',
'404': '404',
'500': '500',
'basic-table': 'Basic Table',
};
let minify = {
collapseWhitespace: false,
minifyCSS: false,
minifyJS: false,
removeComments: true,
useShortDoctype: false,
};
if (manifest.MINIFY) {
minify = {
collapseWhitespace: true,
minifyCSS: true,
minifyJS: true,
removeComments: true,
useShortDoctype: true,
};
}
module.exports = Object.keys(titles).map(title => {
return new HtmlWebpackPlugin({
template: path.join(manifest.paths.src, `${title}.html`),
path: manifest.paths.build,
filename: `${title}.html`,
inject: true,
minify,
});
});
================================================
FILE: webpack/plugins/index.js
================================================
const manifest = require('../manifest');
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
const plugins = [];
plugins.push(
...(require('./htmlPlugin')),
...(require('./internal')),
require('./caseSensitivePlugin'),
require('./extractPlugin'),
require('./copyPlugin')
);
if (manifest.IS_DEVELOPMENT) {
plugins.push(require('./dashboardPlugin'));
}
if (manifest.IS_PRODUCTION) {
plugins.push(require('./copyPlugin'));
}
// Bundle analyzer - run with ANALYZE=true npm run build
if (process.env.ANALYZE === 'true') {
plugins.push(new BundleAnalyzerPlugin({
analyzerMode: 'static',
reportFilename: 'bundle-report.html',
openAnalyzer: true,
}));
}
module.exports = plugins;
================================================
FILE: webpack/plugins/internal.js
================================================
// ------------------
// @Table of Contents
// ------------------
/**
* + @Loading Dependencies
* + @Common Plugins
* + @Merging Production Plugins
* + @Merging Development Plugins
* + @Exporting Module
*/
// ---------------------
// @Loading Dependencies
// ---------------------
const
manifest = require('../manifest'),
webpack = require('webpack');
// ---------------
// @Common Plugins
// ---------------
const
plugins = [];
plugins.push(
// new webpack.DefinePlugin({
// 'process.env': {
// NODE_ENV: JSON.stringify(manifest.NODE_ENV),
// },
// }),
// new webpack.optimize.CommonsChunkPlugin({
// name: 'vendor',
// filename: manifest.outputFiles.vendor,
// minChunks(module) {
// const { context } = module;
// return context && context.indexOf('node_modules') >= 0;
// },
// }),
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery',
'window.jQuery': 'jquery',
// Popper: ['popper.js', 'default'],
})
);
// ---------------------------
// @Merging Production Plugins
// ---------------------------
// if (manifest.IS_PRODUCTION) {
// plugins.push(
// new webpack.optimize.UglifyJsPlugin({
// compress: {
// comparisons : true,
// conditionals : true,
// dead_code : true,
// drop_debugger : true,
// evaluate : true,
// if_return : true,
// join_vars : true,
// screw_ie8 : true,
// sequences : true,
// unused : true,
// warnings : false,
// },
// output: {
// comments: false,
// },
// })
// );
// }
// ----------------------------
// @Merging Development Plugins
// ----------------------------
if (manifest.IS_DEVELOPMENT) {
plugins.push(
// new webpack.NoEmitOnErrorsPlugin(),
// new webpack.NamedModulesPlugin(),
new webpack.HotModuleReplacementPlugin()
);
}
// -----------------
// @Exporting Module
// -----------------
module.exports = plugins;
================================================
FILE: webpack/rules/css.js
================================================
// ------------------
// @Table of Contents
// ------------------
/**
* + @Loading Dependencies
* + @Common Loaders
* + @Merging Production Loaders
* + @Merging Development Loaders
* + @Exporting Module
*/
// ---------------------
// @Loading Dependencies
// ---------------------
const
manifest = require('../manifest'),
ExtractTextPlugin = require('mini-css-extract-plugin');
// ---------------
// @Common Loaders
// ---------------
let rule = {};
const loaders = [
{
loader: 'css-loader',
options: {
sourceMap : manifest.IS_DEVELOPMENT,
importLoaders: 1,
},
},
];
// ---------------------------
// @Merging Production Loaders
// ---------------------------
if (manifest.IS_PRODUCTION) {
rule = {
test: /\.css$/,
use: [{
loader: ExtractTextPlugin.loader,
}].concat(loaders),
};
}
// ----------------------------
// @Merging Development Loaders
// ----------------------------
if (manifest.IS_DEVELOPMENT) {
rule = {
test: /\.css$/,
use: [{
loader: 'style-loader',
}].concat(loaders),
};
}
// -----------------
// @Exporting Module
// -----------------
module.exports = rule;
================================================
FILE: webpack/rules/fonts.js
================================================
module.exports = {
test: /\.(eot|svg|ttf|woff|woff2)$/,
exclude : /(node_modules)/,
type: 'asset/resource',
};
================================================
FILE: webpack/rules/images.js
================================================
module.exports = {
test : /\.(png|gif|jpg?g|svg)$/i,
exclude : /(node_modules)/,
type : 'asset/resource',
generator: {
filename: 'assets/[name][ext]',
},
};
================================================
FILE: webpack/rules/index.js
================================================
module.exports = [
require('./js'),
require('./images'),
require('./css'),
require('./sass'),
require('./fonts'),
];
================================================
FILE: webpack/rules/js.js
================================================
module.exports = {
test : /\.(js|jsx)$/,
exclude : /(node_modules|build|dist\/)/,
use : ['babel-loader'],
};
================================================
FILE: webpack/rules/sass.js
================================================
// ------------------
// @Table of Contents
// ------------------
/**
* + @Loading Dependencies
* + @Common Loaders
* + @Exporting Module
*/
// ---------------------
// @Loading Dependencies
// ---------------------
const
manifest = require('../manifest'),
path = require('path'),
cssNext = require('postcss-preset-env'),
ExtractTextPlugin = require('mini-css-extract-plugin');
// ---------------
// @Common Loaders
// ---------------
const loaders = [
{
loader: 'css-loader',
options: {
sourceMap : manifest.IS_DEVELOPMENT,
},
},
{
loader: 'postcss-loader',
options: {
sourceMap: manifest.IS_DEVELOPMENT,
postcssOptions: {
plugins: [
[
cssNext(),
],
],
},
},
},
{
loader: 'sass-loader',
options: {
sourceMap: manifest.IS_DEVELOPMENT,
api: 'modern-compiler',
sassOptions: {
outputStyle: manifest.MINIFY ? 'compressed' : 'expanded',
includePaths: [
path.join('../../', 'node_modules'),
path.join(manifest.paths.src, 'assets', 'styles'),
path.join(manifest.paths.src, ''),
],
quietDeps: true,
verbose: false,
},
},
},
];
if (manifest.IS_PRODUCTION) {
loaders.unshift(ExtractTextPlugin.loader);
} else {
loaders.unshift({
loader: 'style-loader',
});
}
const rule = {
test: /\.scss$/,
use: loaders,
};
// -----------------
// @Exporting Module
// -----------------
module.exports = rule;
================================================
FILE: webpack.config.js
================================================
const config = require('./webpack/config');
module.exports = config;