faq-accordion.js 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. // ==========================================================================
  2. // FAQ ACCORDION FUNCTIONALITY
  3. // ==========================================================================
  4. class FAQAccordion {
  5. constructor(element, options = {}) {
  6. if (!(element instanceof Element)) {
  7. console.error('FAQAccordion error: Expected a valid Element, got:', element);
  8. return;
  9. }
  10. this.faqSection = element;
  11. this.faqItems = this.faqSection.querySelectorAll('.faq-item');
  12. this.accentColor = this.faqSection.getAttribute('data-color') || '#4f46e5';
  13. this.backgroundColor = this.faqSection.getAttribute('data-bg') || '#ffffff';
  14. this.titleColor = this.faqSection.getAttribute('data-title-color') || '#111827';
  15. this.textColor = this.faqSection.getAttribute('data-text-color') || '#374151';
  16. this.borderStyle = this.faqSection.getAttribute('data-border') || '1px solid #e0e0e0';
  17. this.config = {
  18. allowMultiple: this.faqSection.getAttribute('data-multiple') === 'true',
  19. ...options
  20. };
  21. this.init();
  22. }
  23. init() {
  24. this.applyStyles();
  25. this.bindEvents();
  26. }
  27. applyStyles() {
  28. this.faqItems.forEach(item => {
  29. item.style.backgroundColor = this.backgroundColor;
  30. item.style.border = this.borderStyle;
  31. const questionText = item.querySelector('.question-text');
  32. const answerText = item.querySelector('.faq-answer');
  33. const toggle = item.querySelector('.faq-toggle');
  34. if (questionText) questionText.style.color = this.titleColor;
  35. if (answerText) {
  36. answerText.querySelectorAll('*').forEach(child => {
  37. child.style.color = this.textColor;
  38. });
  39. }
  40. if (toggle) toggle.style.color = this.accentColor;
  41. });
  42. }
  43. bindEvents() {
  44. this.faqItems.forEach((item, index) => {
  45. const question = item.querySelector('.faq-question');
  46. const answer = item.querySelector('.faq-answer');
  47. if (!question || !answer) return;
  48. question.setAttribute('tabindex', '0');
  49. question.setAttribute('aria-expanded', 'false');
  50. question.setAttribute('role', 'button');
  51. question.setAttribute('aria-controls', `faq-answer-${index}`);
  52. answer.setAttribute('id', `faq-answer-${index}`);
  53. answer.style.maxHeight = '0px';
  54. answer.style.overflow = 'hidden';
  55. answer.style.transition = 'max-height 0.4s ease';
  56. question.addEventListener('click', () => this.toggleItem(item));
  57. question.addEventListener('keydown', (e) => {
  58. if (e.key === 'Enter' || e.key === ' ') {
  59. e.preventDefault();
  60. this.toggleItem(item);
  61. }
  62. });
  63. });
  64. }
  65. toggleItem(item) {
  66. const isOpen = item.classList.contains('active');
  67. if (!this.config.allowMultiple) {
  68. this.faqItems.forEach(i => this.closeItem(i));
  69. }
  70. if (!isOpen) {
  71. this.openItem(item);
  72. } else {
  73. this.closeItem(item);
  74. }
  75. }
  76. openItem(item) {
  77. const answer = item.querySelector('.faq-answer');
  78. const question = item.querySelector('.faq-question');
  79. item.classList.add('active');
  80. answer.style.maxHeight = answer.scrollHeight + 'px';
  81. if (question) question.setAttribute('aria-expanded', 'true');
  82. }
  83. closeItem(item) {
  84. const answer = item.querySelector('.faq-answer');
  85. const question = item.querySelector('.faq-question');
  86. item.classList.remove('active');
  87. answer.style.maxHeight = '0px';
  88. if (question) question.setAttribute('aria-expanded', 'false');
  89. }
  90. }
  91. // Initialize on DOM load
  92. document.addEventListener('DOMContentLoaded', () => {
  93. document.querySelectorAll('[data-faq]').forEach(section => {
  94. new FAQAccordion(section);
  95. });
  96. });
  97. // Optional export for modules
  98. if (typeof module !== 'undefined' && module.exports) {
  99. module.exports = FAQAccordion;
  100. }