--- layout: default title: Customization Guide nav_order: 7 --- # Customization Guide {: .no_toc } Learn how to customize and extend Gentelella Admin Template to match your brand and requirements {: .fs-6 .fw-300 } ## Table of contents {: .no_toc .text-delta } 1. TOC {:toc} --- ## Branding and Theming ### Color Scheme Customization #### Primary Colors Override Bootstrap variables in `src/scss/variables.scss`: ```scss // Brand colors $primary: #73879C; // Main brand color $secondary: #6c757d; // Secondary color $success: #26B99A; // Success actions $info: #3498DB; // Information $warning: #F39C12; // Warnings $danger: #E74C3C; // Errors // Sidebar colors $sidebar-bg: #2A3F54; $sidebar-text: #E7E7E7; $sidebar-text-hover: #ffffff; $sidebar-active-bg: $primary; // Dashboard colors $dashboard-bg: #F7F7F7; $card-bg: #ffffff; $card-border: #E6E9ED; // Text colors $text-primary: #73879C; $text-secondary: #ABB1B7; $text-dark: #566573; ``` #### Dark Theme Support Create `src/scss/themes/_dark.scss`: ```scss // Dark theme variables [data-theme="dark"] { --bs-body-bg: #1a1a1a; --bs-body-color: #ffffff; --bs-card-bg: #2d2d2d; --bs-border-color: #404040; // Sidebar dark theme .left_col { background: #0F1419; .nav-link { color: #CCCCCC; &:hover { color: #ffffff; background: rgba(255, 255, 255, 0.1); } &.active { background: var(--bs-primary); color: #ffffff; } } } // Cards and panels .x_panel { background: var(--bs-card-bg); border: 1px solid var(--bs-border-color); .x_title { border-bottom: 1px solid var(--bs-border-color); h2 { color: var(--bs-body-color); } } } // Tables .table { --bs-table-bg: var(--bs-card-bg); --bs-table-border-color: var(--bs-border-color); color: var(--bs-body-color); } // Forms .form-control { background-color: #3d3d3d; border-color: var(--bs-border-color); color: var(--bs-body-color); &:focus { background-color: #4d4d4d; border-color: var(--bs-primary); } } } ``` #### Theme Toggle Implementation ```javascript // src/js/theme-toggle.js class ThemeToggle { constructor() { this.theme = localStorage.getItem('theme') || 'light'; this.init(); } init() { // Apply saved theme document.documentElement.setAttribute('data-theme', this.theme); // Create toggle button this.createToggleButton(); // Listen for toggle events document.addEventListener('theme-toggle', this.toggle.bind(this)); } createToggleButton() { const button = document.createElement('button'); button.className = 'btn btn-outline-secondary theme-toggle'; button.innerHTML = this.theme === 'dark' ? '' : ''; button.addEventListener('click', () => this.toggle()); // Add to navbar const navbar = document.querySelector('.navbar-nav'); if (navbar) { const li = document.createElement('li'); li.className = 'nav-item'; li.appendChild(button); navbar.appendChild(li); } } toggle() { this.theme = this.theme === 'light' ? 'dark' : 'light'; document.documentElement.setAttribute('data-theme', this.theme); localStorage.setItem('theme', this.theme); // Update button icon const button = document.querySelector('.theme-toggle'); if (button) { button.innerHTML = this.theme === 'dark' ? '' : ''; } // Trigger custom event document.dispatchEvent(new CustomEvent('theme-changed', { detail: { theme: this.theme } })); } getTheme() { return this.theme; } } // Initialize theme toggle new ThemeToggle(); ``` ### Logo and Branding #### Custom Logo Implementation ```scss // src/scss/components/_logo.scss .site_title { display: flex; align-items: center; padding: 15px 20px; color: $sidebar-text; text-decoration: none; .logo { width: 32px; height: 32px; margin-right: 10px; img { width: 100%; height: 100%; object-fit: contain; } } .brand-text { font-size: 18px; font-weight: 600; .brand-suffix { font-size: 12px; font-weight: 400; opacity: 0.8; display: block; line-height: 1; } } } // Responsive logo @media (max-width: 768px) { .site_title { .brand-text { display: none; } } } ``` ```html Your Brand Admin Panel ``` ### Typography Customization #### Custom Font Integration ```scss // src/scss/base/_typography.scss // Import custom fonts @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap'); // Typography variables $font-family-base: 'Inter', 'Segoe UI', Roboto, sans-serif; $font-family-heading: 'Inter', 'Segoe UI', Roboto, sans-serif; $font-family-monospace: 'SF Mono', Monaco, 'Cascadia Code', monospace; // Font sizes $font-size-xs: 0.75rem; // 12px $font-size-sm: 0.875rem; // 14px $font-size-base: 1rem; // 16px $font-size-lg: 1.125rem; // 18px $font-size-xl: 1.25rem; // 20px // Font weights $font-weight-light: 300; $font-weight-normal: 400; $font-weight-medium: 500; $font-weight-semibold: 600; $font-weight-bold: 700; // Line heights $line-height-tight: 1.25; $line-height-normal: 1.5; $line-height-relaxed: 1.75; // Apply typography body { font-family: $font-family-base; font-size: $font-size-base; font-weight: $font-weight-normal; line-height: $line-height-normal; } // Headings h1, h2, h3, h4, h5, h6 { font-family: $font-family-heading; font-weight: $font-weight-semibold; line-height: $line-height-tight; margin-bottom: 0.5em; } h1 { font-size: 2.5rem; } h2 { font-size: 2rem; } h3 { font-size: 1.75rem; } h4 { font-size: 1.5rem; } h5 { font-size: 1.25rem; } h6 { font-size: 1rem; } // Code and monospace code, pre { font-family: $font-family-monospace; } ``` --- ## Layout Customization ### Sidebar Modifications #### Collapsible Sidebar ```javascript // src/js/sidebar.js class Sidebar { constructor() { this.sidebar = document.querySelector('.left_col'); this.mainContent = document.querySelector('.right_col'); this.toggleBtn = document.querySelector('.sidebar-toggle'); this.isCollapsed = localStorage.getItem('sidebar-collapsed') === 'true'; this.init(); } init() { // Apply saved state if (this.isCollapsed) { this.collapse(); } // Create toggle button if it doesn't exist if (!this.toggleBtn) { this.createToggleButton(); } // Add event listeners this.toggleBtn?.addEventListener('click', () => this.toggle()); // Handle responsive behavior this.handleResize(); window.addEventListener('resize', () => this.handleResize()); } createToggleButton() { const button = document.createElement('button'); button.className = 'btn btn-link sidebar-toggle'; button.innerHTML = ''; // Add to navbar const navbar = document.querySelector('.navbar'); if (navbar) { navbar.insertBefore(button, navbar.firstChild); } this.toggleBtn = button; } toggle() { if (this.isCollapsed) { this.expand(); } else { this.collapse(); } } collapse() { this.sidebar?.classList.add('collapsed'); this.mainContent?.classList.add('sidebar-collapsed'); this.isCollapsed = true; localStorage.setItem('sidebar-collapsed', 'true'); // Update toggle button icon if (this.toggleBtn) { this.toggleBtn.innerHTML = ''; } } expand() { this.sidebar?.classList.remove('collapsed'); this.mainContent?.classList.remove('sidebar-collapsed'); this.isCollapsed = false; localStorage.setItem('sidebar-collapsed', 'false'); // Update toggle button icon if (this.toggleBtn) { this.toggleBtn.innerHTML = ''; } } handleResize() { const width = window.innerWidth; // Auto-collapse on mobile if (width < 768) { this.collapse(); } else if (width > 1200 && this.isCollapsed) { this.expand(); } } } // Initialize sidebar new Sidebar(); ``` ```scss // src/scss/components/_sidebar.scss .left_col { width: 230px; transition: all 0.3s ease; &.collapsed { width: 70px; .nav_title { .brand-text { display: none; } } .main_menu_side { .nav > li > a { text-align: center; padding: 12px 0; .menu-text { display: none; } .fa { margin-right: 0; } } .child_menu { display: none !important; } } } } .right_col { margin-left: 230px; transition: all 0.3s ease; &.sidebar-collapsed { margin-left: 70px; } } @media (max-width: 768px) { .left_col { transform: translateX(-100%); &.mobile-show { transform: translateX(0); } } .right_col { margin-left: 0; } } ``` #### Custom Menu Items ```javascript // src/js/menu-builder.js class MenuBuilder { constructor(menuConfig) { this.config = menuConfig; this.menuContainer = document.querySelector('#sidebar-menu'); this.buildMenu(); } buildMenu() { if (!this.menuContainer) return; this.menuContainer.innerHTML = ''; this.config.sections.forEach(section => { const sectionElement = this.createSection(section); this.menuContainer.appendChild(sectionElement); }); } createSection(section) { const sectionDiv = document.createElement('div'); sectionDiv.className = 'menu_section'; if (section.title) { const title = document.createElement('h3'); title.textContent = section.title; sectionDiv.appendChild(title); } const menuList = document.createElement('ul'); menuList.className = 'nav side-menu'; section.items.forEach(item => { const menuItem = this.createMenuItem(item); menuList.appendChild(menuItem); }); sectionDiv.appendChild(menuList); return sectionDiv; } createMenuItem(item) { const li = document.createElement('li'); const a = document.createElement('a'); // Set link properties if (item.url) { a.href = item.url; } // Add icon if (item.icon) { const icon = document.createElement('i'); icon.className = `fa fa-${item.icon}`; a.appendChild(icon); } // Add text const textSpan = document.createElement('span'); textSpan.className = 'menu-text'; textSpan.textContent = item.label; a.appendChild(textSpan); // Add submenu indicator if (item.children && item.children.length > 0) { const chevron = document.createElement('span'); chevron.className = 'fa fa-chevron-down'; a.appendChild(chevron); // Create submenu const submenu = this.createSubmenu(item.children); li.appendChild(submenu); } // Add click handler for submenus a.addEventListener('click', (e) => { if (item.children && item.children.length > 0) { e.preventDefault(); this.toggleSubmenu(li); } }); li.appendChild(a); return li; } createSubmenu(items) { const ul = document.createElement('ul'); ul.className = 'nav child_menu'; ul.style.display = 'none'; items.forEach(item => { const li = document.createElement('li'); const a = document.createElement('a'); a.href = item.url || '#'; a.textContent = item.label; li.appendChild(a); ul.appendChild(li); }); return ul; } toggleSubmenu(parentLi) { const submenu = parentLi.querySelector('.child_menu'); const chevron = parentLi.querySelector('.fa-chevron-down, .fa-chevron-up'); if (submenu.style.display === 'none') { submenu.style.display = 'block'; chevron.className = chevron.className.replace('chevron-down', 'chevron-up'); } else { submenu.style.display = 'none'; chevron.className = chevron.className.replace('chevron-up', 'chevron-down'); } } } // Menu configuration const menuConfig = { sections: [ { title: 'General', items: [ { label: 'Dashboard', icon: 'home', children: [ { label: 'Dashboard 1', url: 'index.html' }, { label: 'Dashboard 2', url: 'index2.html' }, { label: 'Dashboard 3', url: 'index3.html' } ] }, { label: 'Analytics', icon: 'bar-chart-o', url: 'analytics.html' } ] }, { title: 'Forms', items: [ { label: 'Form Elements', icon: 'edit', url: 'form.html' }, { label: 'Form Validation', icon: 'check-square-o', url: 'form_validation.html' } ] } ] }; // Initialize menu new MenuBuilder(menuConfig); ``` ### Header Customization #### Custom Navigation Bar ```scss // src/scss/components/_navbar.scss .nav_menu { background: #ffffff; border-bottom: 1px solid #E6E9ED; box-shadow: 0 2px 4px rgba(0,0,0,0.1); .navbar-nav { align-items: center; .nav-item { margin: 0 5px; .nav-link { padding: 8px 12px; border-radius: 6px; transition: all 0.2s ease; &:hover { background: rgba(115, 135, 156, 0.1); color: $primary; } } // User dropdown &.dropdown { .dropdown-menu { border: none; box-shadow: 0 8px 24px rgba(0,0,0,0.15); border-radius: 8px; margin-top: 8px; .dropdown-item { padding: 12px 20px; &:hover { background: rgba(115, 135, 156, 0.1); } } } } } } // Breadcrumb .breadcrumb { background: transparent; margin: 0; padding: 0; .breadcrumb-item { color: #566573; &.active { color: $primary; font-weight: 500; } a { color: #566573; text-decoration: none; &:hover { color: $primary; } } } } } ``` #### Search Functionality ```javascript // src/js/search.js class GlobalSearch { constructor() { this.searchInput = document.getElementById('global-search'); this.searchResults = document.getElementById('search-results'); this.searchData = []; this.init(); } async init() { if (!this.searchInput) return; // Load search data await this.loadSearchData(); // Add event listeners this.searchInput.addEventListener('input', this.debounce(this.handleSearch.bind(this), 300)); this.searchInput.addEventListener('focus', this.showResults.bind(this)); document.addEventListener('click', this.hideResults.bind(this)); } async loadSearchData() { // Load searchable content this.searchData = [ { title: 'Dashboard', url: 'index.html', category: 'Page' }, { title: 'Form Elements', url: 'form.html', category: 'Page' }, { title: 'Tables', url: 'tables.html', category: 'Page' }, { title: 'Charts', url: 'chartjs.html', category: 'Page' }, // Add more searchable items ]; } handleSearch(event) { const query = event.target.value.toLowerCase().trim(); if (query.length < 2) { this.hideResults(); return; } const results = this.searchData.filter(item => item.title.toLowerCase().includes(query) || item.category.toLowerCase().includes(query) ).slice(0, 10); this.displayResults(results, query); } displayResults(results, query) { if (!this.searchResults) return; this.searchResults.innerHTML = ''; if (results.length === 0) { const noResults = document.createElement('div'); noResults.className = 'search-no-results'; noResults.textContent = 'No results found'; this.searchResults.appendChild(noResults); } else { results.forEach(result => { const item = this.createResultItem(result, query); this.searchResults.appendChild(item); }); } this.showResults(); } createResultItem(result, query) { const item = document.createElement('a'); item.className = 'search-result-item'; item.href = result.url; const title = document.createElement('div'); title.className = 'search-result-title'; title.innerHTML = this.highlightQuery(result.title, query); const category = document.createElement('div'); category.className = 'search-result-category'; category.textContent = result.category; item.appendChild(title); item.appendChild(category); return item; } highlightQuery(text, query) { const regex = new RegExp(`(${query})`, 'gi'); return text.replace(regex, '$1'); } showResults() { if (this.searchResults) { this.searchResults.style.display = 'block'; } } hideResults(event) { if (event && this.searchInput.contains(event.target)) return; if (this.searchResults) { this.searchResults.style.display = 'none'; } } debounce(func, wait) { let timeout; return function executedFunction(...args) { const later = () => { clearTimeout(timeout); func(...args); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; } } // Initialize search new GlobalSearch(); ``` --- ## Component Customization ### Custom Dashboard Widgets #### Widget Factory ```javascript // src/js/widgets/widget-factory.js class WidgetFactory { static createWidget(type, config) { switch (type) { case 'stat': return new StatWidget(config); case 'chart': return new ChartWidget(config); case 'list': return new ListWidget(config); case 'progress': return new ProgressWidget(config); default: throw new Error(`Unknown widget type: ${type}`); } } } class BaseWidget { constructor(config) { this.config = config; this.container = null; } render(container) { this.container = container; this.container.innerHTML = this.template(); this.afterRender(); } template() { return '
Base Widget
'; } afterRender() { // Override in subclasses } destroy() { if (this.container) { this.container.innerHTML = ''; } } } class StatWidget extends BaseWidget { template() { return `

${this.config.title}

${this.config.value}
${this.config.label}
${this.config.change ? `
${Math.abs(this.config.change)}%
` : ''}
`; } } class ChartWidget extends BaseWidget { template() { return `

${this.config.title}

`; } afterRender() { this.initChart(); } async initChart() { const { Chart } = await import('chart.js/auto'); const ctx = document.getElementById(`chart-${this.config.id}`); new Chart(ctx, { type: this.config.chartType || 'line', data: this.config.data, options: { responsive: true, maintainAspectRatio: false, ...this.config.options } }); } } ``` #### Widget Configuration ```javascript // src/js/dashboard-config.js const dashboardConfig = { widgets: [ { id: 'users-stat', type: 'stat', grid: { x: 0, y: 0, w: 3, h: 1 }, config: { title: 'Total Users', value: '2,564', label: 'Active Users', icon: 'users', change: 12.5 } }, { id: 'revenue-stat', type: 'stat', grid: { x: 3, y: 0, w: 3, h: 1 }, config: { title: 'Revenue', value: '$52,147', label: 'This Month', icon: 'dollar', change: -3.2 } }, { id: 'sales-chart', type: 'chart', grid: { x: 0, y: 1, w: 6, h: 2 }, config: { title: 'Sales Overview', chartType: 'line', data: { labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'], datasets: [{ label: 'Sales', data: [12, 19, 3, 5, 2, 3], borderColor: '#73879C', backgroundColor: 'rgba(115, 135, 156, 0.1)' }] } } } ] }; // Initialize dashboard class Dashboard { constructor(config) { this.config = config; this.widgets = new Map(); this.container = document.getElementById('dashboard-container'); } init() { this.createGrid(); this.renderWidgets(); } createGrid() { this.container.className = 'dashboard-grid'; } renderWidgets() { this.config.widgets.forEach(widgetConfig => { const widget = WidgetFactory.createWidget( widgetConfig.type, widgetConfig.config ); const widgetContainer = this.createWidgetContainer(widgetConfig); widget.render(widgetContainer); this.widgets.set(widgetConfig.id, widget); }); } createWidgetContainer(config) { const container = document.createElement('div'); container.className = 'dashboard-widget'; container.style.gridColumn = `${config.grid.x + 1} / ${config.grid.x + config.grid.w + 1}`; container.style.gridRow = `${config.grid.y + 1} / ${config.grid.y + config.grid.h + 1}`; this.container.appendChild(container); return container; } } // Initialize dashboard new Dashboard(dashboardConfig).init(); ``` ### Form Builder #### Dynamic Form Generator ```javascript // src/js/forms/form-builder.js class FormBuilder { constructor(container, schema) { this.container = container; this.schema = schema; this.fields = new Map(); } build() { const form = document.createElement('form'); form.className = 'dynamic-form'; form.setAttribute('data-validate', 'true'); this.schema.fields.forEach(fieldConfig => { const field = this.createField(fieldConfig); form.appendChild(field); }); // Add submit button if (this.schema.submit) { const submitBtn = this.createSubmitButton(this.schema.submit); form.appendChild(submitBtn); } this.container.appendChild(form); this.initializeValidation(); return form; } createField(config) { const fieldContainer = document.createElement('div'); fieldContainer.className = 'form-group row mb-3'; // Create label if (config.label) { const label = document.createElement('label'); label.className = 'col-form-label col-md-3 col-sm-3'; label.textContent = config.label; label.setAttribute('for', config.name); fieldContainer.appendChild(label); } // Create field wrapper const fieldWrapper = document.createElement('div'); fieldWrapper.className = 'col-md-6 col-sm-6'; // Create field based on type const field = this.createFieldByType(config); fieldWrapper.appendChild(field); // Add help text if (config.help) { const helpText = document.createElement('small'); helpText.className = 'form-text text-muted'; helpText.textContent = config.help; fieldWrapper.appendChild(helpText); } fieldContainer.appendChild(fieldWrapper); this.fields.set(config.name, field); return fieldContainer; } createFieldByType(config) { switch (config.type) { case 'text': case 'email': case 'password': case 'number': return this.createInput(config); case 'textarea': return this.createTextarea(config); case 'select': return this.createSelect(config); case 'checkbox': return this.createCheckbox(config); case 'radio': return this.createRadioGroup(config); case 'file': return this.createFileInput(config); case 'date': return this.createDateInput(config); default: return this.createInput(config); } } createInput(config) { const input = document.createElement('input'); input.type = config.type || 'text'; input.name = config.name; input.id = config.name; input.className = 'form-control'; if (config.placeholder) input.placeholder = config.placeholder; if (config.value) input.value = config.value; if (config.required) input.required = true; if (config.pattern) input.pattern = config.pattern; if (config.min) input.min = config.min; if (config.max) input.max = config.max; return input; } createSelect(config) { const select = document.createElement('select'); select.name = config.name; select.id = config.name; select.className = 'form-control'; if (config.multiple) { select.multiple = true; select.className += ' select2'; } if (config.placeholder) { const placeholderOption = document.createElement('option'); placeholderOption.value = ''; placeholderOption.textContent = config.placeholder; placeholderOption.disabled = true; placeholderOption.selected = true; select.appendChild(placeholderOption); } if (config.options) { config.options.forEach(option => { const optionElement = document.createElement('option'); optionElement.value = option.value; optionElement.textContent = option.label; if (option.selected) optionElement.selected = true; select.appendChild(optionElement); }); } return select; } createTextarea(config) { const textarea = document.createElement('textarea'); textarea.name = config.name; textarea.id = config.name; textarea.className = 'form-control'; textarea.rows = config.rows || 4; if (config.placeholder) textarea.placeholder = config.placeholder; if (config.value) textarea.value = config.value; if (config.required) textarea.required = true; return textarea; } getData() { const data = {}; this.fields.forEach((field, name) => { if (field.type === 'checkbox') { data[name] = field.checked; } else if (field.type === 'radio') { const checked = document.querySelector(`input[name="${name}"]:checked`); data[name] = checked ? checked.value : null; } else { data[name] = field.value; } }); return data; } setData(data) { Object.entries(data).forEach(([name, value]) => { const field = this.fields.get(name); if (field) { if (field.type === 'checkbox') { field.checked = value; } else { field.value = value; } } }); } initializeValidation() { // Initialize form validation if Parsley is available if (window.Parsley) { const form = this.container.querySelector('form'); $(form).parsley(); } } } // Form schema example const userFormSchema = { fields: [ { name: 'firstName', type: 'text', label: 'First Name', placeholder: 'Enter first name', required: true }, { name: 'email', type: 'email', label: 'Email Address', placeholder: 'Enter email', required: true }, { name: 'role', type: 'select', label: 'Role', placeholder: 'Select role', options: [ { value: 'admin', label: 'Administrator' }, { value: 'user', label: 'User' }, { value: 'moderator', label: 'Moderator' } ], required: true }, { name: 'bio', type: 'textarea', label: 'Biography', placeholder: 'Tell us about yourself', rows: 4 } ], submit: { text: 'Create User', className: 'btn btn-primary' } }; // Usage const formContainer = document.getElementById('form-container'); const formBuilder = new FormBuilder(formContainer, userFormSchema); const form = formBuilder.build(); ``` --- ## Advanced Customization ### Plugin System #### Plugin Architecture ```javascript // src/js/core/plugin-system.js class PluginSystem { constructor() { this.plugins = new Map(); this.hooks = new Map(); } registerPlugin(name, plugin) { if (this.plugins.has(name)) { console.warn(`Plugin ${name} already registered`); return; } // Initialize plugin if (typeof plugin.init === 'function') { plugin.init(this); } this.plugins.set(name, plugin); console.log(`Plugin ${name} registered successfully`); } getPlugin(name) { return this.plugins.get(name); } addHook(hookName, callback, priority = 10) { if (!this.hooks.has(hookName)) { this.hooks.set(hookName, []); } this.hooks.get(hookName).push({ callback, priority }); // Sort by priority this.hooks.get(hookName).sort((a, b) => a.priority - b.priority); } async executeHook(hookName, data = {}) { if (!this.hooks.has(hookName)) { return data; } const hooks = this.hooks.get(hookName); let result = data; for (const hook of hooks) { try { const hookResult = await hook.callback(result); if (hookResult !== undefined) { result = hookResult; } } catch (error) { console.error(`Error in hook ${hookName}:`, error); } } return result; } removeHook(hookName, callback) { if (!this.hooks.has(hookName)) return; const hooks = this.hooks.get(hookName); const index = hooks.findIndex(hook => hook.callback === callback); if (index > -1) { hooks.splice(index, 1); } } } // Global plugin system instance window.GentelellaPlugins = new PluginSystem(); ``` #### Example Plugin ```javascript // src/js/plugins/notification-plugin.js const NotificationPlugin = { name: 'notifications', init(pluginSystem) { this.pluginSystem = pluginSystem; this.notifications = []; this.container = null; this.createContainer(); this.bindHooks(); }, createContainer() { this.container = document.createElement('div'); this.container.id = 'notification-container'; this.container.className = 'notification-container'; document.body.appendChild(this.container); }, bindHooks() { // Hook into form submissions this.pluginSystem.addHook('form.submit.success', (data) => { this.show('Form submitted successfully!', 'success'); return data; }); this.pluginSystem.addHook('form.submit.error', (data) => { this.show('Error submitting form', 'error'); return data; }); }, show(message, type = 'info', duration = 5000) { const notification = document.createElement('div'); notification.className = `notification notification-${type}`; notification.innerHTML = `
${message}
`; // Add close functionality const closeBtn = notification.querySelector('.notification-close'); closeBtn.addEventListener('click', () => this.remove(notification)); // Auto remove after duration setTimeout(() => this.remove(notification), duration); this.container.appendChild(notification); this.notifications.push(notification); // Animate in requestAnimationFrame(() => { notification.classList.add('notification-show'); }); }, remove(notification) { notification.classList.add('notification-hide'); setTimeout(() => { if (notification.parentNode) { notification.parentNode.removeChild(notification); } const index = this.notifications.indexOf(notification); if (index > -1) { this.notifications.splice(index, 1); } }, 300); }, getIcon(type) { const icons = { success: 'check-circle', error: 'exclamation-circle', warning: 'exclamation-triangle', info: 'info-circle' }; return icons[type] || icons.info; } }; // Register plugin window.GentelellaPlugins.registerPlugin('notifications', NotificationPlugin); ``` --- ## Next Steps - **[API Integration]({{ site.baseurl }}/docs/api-integration/)** - Connect with backend APIs - **[Security Guide]({{ site.baseurl }}/docs/security/)** - Implement security best practices - **[Testing Guide]({{ site.baseurl }}/docs/testing/)** - Test your customizations --- {: .highlight } 💡 **Pro Tip**: Start with small customizations and gradually build complexity. Always test your changes across different screen sizes and browsers to ensure compatibility.