PageSourceSearch

https://cdn2.futurefund.com/assets/honey/ui/controllers/faq_controller-88b1e432.js

js futurefund.com collected 2026-09-24 11:52:39 UTC 3,255 bytes, 121 lines download raw bytes

1/**
2 * @class FaqController
3 * @extends Controller
4 * @description
5 * Accordion-style expand/collapse for FAQ sections or collapsible content.
6 * Uses max-height animation for smooth open/close transitions.
7 *
8 * Features:
9 * - Smooth height animation using CSS transitions
10 * - Sets "open" attribute on element for CSS styling
11 * - ARIA accessibility support
12 * - Individual or grouped FAQ items
13 *
14 * @example <caption>Basic FAQ item</caption>
15 * <div data-controller="faq">
16 *   <button data-action="click->faq#toggle" class="flex justify-between w-full">
17 *     <span>What is your return policy?</span>
18 *     <i class="fa-chevron-down transition-transform [:is([open])_&]:rotate-180"></i>
19 *   </button>
20 *
21 *   <div data-faq-target="content" class="overflow-hidden transition-all duration-300">
22 *     <p class="py-4">We offer a 30-day money-back guarantee...</p>
23 *   </div>
24 * </div>
25 *
26 * @example <caption>Multiple FAQ items</caption>
27 * - faqs.each do |faq|
28 *   div[data-controller="faq"]
29 *     button[data-action="click->faq#toggle"]= faq.question
30 *     div[data-faq-target="content"]= faq.answer
31 */
32import { Controller } from '@hotwired/stimulus'
33
34export default class FaqController extends Controller {
35  /**
36   * Stimulus targets for this controller.
37   * @static
38   * @property {string[]} targets
39   * @property {HTMLElement} contentTarget - The collapsible content container
40   * @property {HTMLElement} triggerTarget - Optional trigger element for ARIA (optional)
41   */
42  static targets = ['content', 'trigger']
43
44  /**
45   * Whether the FAQ item is currently expanded.
46   * @type {boolean}
47   */
48  isOpen = false
49
50  /**
51   * Initializes the controller with content hidden (max-height: 0).
52   * Called automatically when the controller connects to the DOM.
53   *
54   * @method connect
55   * @returns {void}
56   */
57  connect() {
58    if (!this.hasContentTarget) return
59
60    this.isOpen = false
61    this.contentTarget.classList.remove('hidden')
62    this.contentTarget.style.maxHeight = '0px'
63  }
64
65  /**
66   * Toggles between open and closed states.
67   *
68   * @method toggle
69   * @returns {void}
70   *
71   * @example
72   * <button data-action="click->faq#toggle">Toggle FAQ</button>
73   */
74  toggle() {
75    if (!this.hasContentTarget) return
76
77    if (this.isOpen) {
78      this.hide()
79    } else {
80      this.open()
81    }
82  }
83
84  /**
85   * Opens the FAQ content with animation.
86   * Sets the "open" attribute on the element for CSS styling.
87   *
88   * @method open
89   * @returns {void}
90   */
91  open() {
92    if (!this.hasContentTarget || this.isOpen) return
93
94    this.isOpen = true
95    this.contentTarget.style.maxHeight = this.contentTarget.scrollHeight + 'px'
96    this.element.setAttribute('open', '')
97
98    if (this.hasTriggerTarget) {
99      this.triggerTarget.setAttribute('aria-expanded', 'true')
100    }
101  }
102
103  /**
104   * Closes the FAQ content with animation.
105   * Removes the "open" attribute from the element.
106   *
107   * @method hide
108   * @returns {void}
109   */
110  hide() {
111    if (!this.hasContentTarget || !this.isOpen) return
112
113    this.isOpen = false
114    this.contentTarget.style.maxHeight = '0px'
115    this.element.removeAttribute('open')
116
117    if (this.hasTriggerTarget) {
118      this.triggerTarget.setAttribute('aria-expanded', 'false')
119    }
120  }
121}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.