forked from izu/student-web-if-development-kit
176 lines
5.6 KiB
JavaScript
176 lines
5.6 KiB
JavaScript
export class CustomDropdown {
|
|
constructor({ container, options, defaultValue, onChange, prefixIcon }) {
|
|
this.container = container;
|
|
this.options = options; // Array of { value, text, i18nKey }
|
|
this.onChange = onChange;
|
|
this.currentValue = defaultValue || options[0]?.value;
|
|
this.prefixIcon = prefixIcon; // optional class name like 'fas fa-sort'
|
|
this.isOpen = false;
|
|
|
|
this.elements = {};
|
|
this._build();
|
|
this._bindEvents();
|
|
}
|
|
|
|
_build() {
|
|
this.container.innerHTML = '';
|
|
this.container.classList.add('custom-dropdown');
|
|
|
|
// Trigger Button
|
|
const trigger = document.createElement('button');
|
|
trigger.className = 'custom-dropdown-trigger';
|
|
trigger.setAttribute('aria-haspopup', 'listbox');
|
|
trigger.setAttribute('aria-expanded', 'false');
|
|
trigger.type = 'button';
|
|
|
|
const triggerLeft = document.createElement('div');
|
|
triggerLeft.className = 'trigger-left-content';
|
|
triggerLeft.style.display = 'flex';
|
|
triggerLeft.style.alignItems = 'center';
|
|
triggerLeft.style.gap = '10px';
|
|
|
|
if (this.prefixIcon) {
|
|
const icon = document.createElement('i');
|
|
icon.className = this.prefixIcon;
|
|
triggerLeft.appendChild(icon);
|
|
}
|
|
|
|
const labelSpan = document.createElement('span');
|
|
labelSpan.className = 'trigger-label';
|
|
|
|
// Find default text
|
|
const defaultOpt = this.options.find(opt => opt.value === this.currentValue) || this.options[0];
|
|
labelSpan.textContent = defaultOpt ? defaultOpt.text : '';
|
|
if (defaultOpt?.i18nKey) {
|
|
labelSpan.setAttribute('data-tp-i18n', defaultOpt.i18nKey);
|
|
}
|
|
|
|
triggerLeft.appendChild(labelSpan);
|
|
trigger.appendChild(triggerLeft);
|
|
|
|
const arrowIcon = document.createElement('i');
|
|
arrowIcon.className = 'fas fa-chevron-down trigger-arrow';
|
|
|
|
trigger.appendChild(arrowIcon);
|
|
this.container.appendChild(trigger);
|
|
|
|
// Menu wrapper
|
|
const menu = document.createElement('div');
|
|
menu.className = 'custom-dropdown-menu';
|
|
menu.setAttribute('role', 'listbox');
|
|
|
|
// Populate options
|
|
this.options.forEach(opt => {
|
|
const item = document.createElement('div');
|
|
item.className = 'custom-dropdown-item' + (opt.value === this.currentValue ? ' active' : '');
|
|
item.setAttribute('role', 'option');
|
|
item.setAttribute('data-value', opt.value);
|
|
|
|
const itemText = document.createElement('span');
|
|
itemText.textContent = opt.text;
|
|
if (opt.i18nKey) {
|
|
itemText.setAttribute('data-tp-i18n', opt.i18nKey);
|
|
}
|
|
|
|
item.appendChild(itemText);
|
|
menu.appendChild(item);
|
|
});
|
|
|
|
this.container.appendChild(menu);
|
|
|
|
this.elements = { trigger, labelSpan, menu };
|
|
}
|
|
|
|
_bindEvents() {
|
|
// Toggle dropdown on trigger click
|
|
this.elements.trigger.addEventListener('click', (e) => {
|
|
e.stopPropagation();
|
|
this.toggle();
|
|
});
|
|
|
|
// Handle item selection
|
|
this.elements.menu.addEventListener('click', (e) => {
|
|
const item = e.target.closest('.custom-dropdown-item');
|
|
if (!item) return;
|
|
|
|
const value = item.getAttribute('data-value');
|
|
this.select(value);
|
|
this.close();
|
|
});
|
|
|
|
// Close when clicking outside
|
|
document.addEventListener('click', () => {
|
|
this.close();
|
|
});
|
|
|
|
// Close on escape key
|
|
document.addEventListener('keydown', (e) => {
|
|
if (e.key === 'Escape') this.close();
|
|
});
|
|
}
|
|
|
|
toggle() {
|
|
if (this.isOpen) {
|
|
this.close();
|
|
} else {
|
|
this.open();
|
|
}
|
|
}
|
|
|
|
open() {
|
|
// Close other custom dropdowns
|
|
document.querySelectorAll('.custom-dropdown.active').forEach(dropdown => {
|
|
if (dropdown !== this.container) {
|
|
dropdown.classList.remove('active');
|
|
dropdown.querySelector('.custom-dropdown-trigger')?.setAttribute('aria-expanded', 'false');
|
|
}
|
|
});
|
|
|
|
this.isOpen = true;
|
|
this.container.classList.add('active');
|
|
this.elements.trigger.setAttribute('aria-expanded', 'true');
|
|
}
|
|
|
|
close() {
|
|
this.isOpen = false;
|
|
this.container.classList.remove('active');
|
|
this.elements.trigger.setAttribute('aria-expanded', 'false');
|
|
}
|
|
|
|
select(value) {
|
|
if (value === this.currentValue) return;
|
|
|
|
this.currentValue = value;
|
|
|
|
// Update active class in menu items
|
|
this.elements.menu.querySelectorAll('.custom-dropdown-item').forEach(item => {
|
|
const isCurrent = item.getAttribute('data-value') === value;
|
|
item.classList.toggle('active', isCurrent);
|
|
});
|
|
|
|
// Update trigger label
|
|
const selectedOpt = this.options.find(opt => opt.value === value);
|
|
if (selectedOpt) {
|
|
this.elements.labelSpan.textContent = selectedOpt.text;
|
|
if (selectedOpt.i18nKey) {
|
|
this.elements.labelSpan.setAttribute('data-tp-i18n', selectedOpt.i18nKey);
|
|
} else {
|
|
this.elements.labelSpan.removeAttribute('data-tp-i18n');
|
|
}
|
|
}
|
|
|
|
// Trigger callback
|
|
if (typeof this.onChange === 'function') {
|
|
this.onChange(value);
|
|
}
|
|
}
|
|
|
|
setValue(value) {
|
|
this.select(value);
|
|
}
|
|
|
|
getValue() {
|
|
return this.currentValue;
|
|
}
|
|
}
|