page Accordioncomponent { seo { title = "Accordion component" description = "Theme-aware, responsive accordion component." } view {
W WRNexusJS
base component

Accordion

Theme-aware, responsive accordion component.

@wrnexus/ui 0.5.017 props0 slots3 events

Usage

Components are auto-discovered. Use the component directly in any .wrn view:

<Accordion
  size="default"
  color="primary"
  variant="default"
  id="accordion"
  items="[]"
/>

Legacy mount name: data-component="Accordion".

Props and attributes

PropTypeRequirementDefault
sizestringOptional"default"
colorstringOptional"primary"
variantstringOptional"default"
classstringOptional""
idstringOptional"accordion"
itemsstringOptional[]
defaultOpenstringOptional[]
multiplebooleanOptionalfalse
alwaysOpenbooleanOptionalfalse
disabledbooleanOptionalfalse
indicatorstringOptional"plus"
indicatorPositionstringOptional"start"
showIndicatorbooleanOptionaltrue
borderedbooleanOptionalfalse
separatedbooleanOptionalfalse
flushbooleanOptionalfalse
contentItalicbooleanOptionalfalse

Slots

This component does not declare a slot.

Events

  • change
  • open
  • close

Source contract

Installed source: components/Accordion.wrn

component Accordion {
  props {
    size = "default"
    color = "primary"
    variant = "default"
    class = ""

    id = "accordion"
    items = []
    defaultOpen = []
    multiple = false
    alwaysOpen = false
    disabled = false

    indicator = "plus"
    indicatorPosition = "start"
    showIndicator = true
    bordered = false
    separated = false
    flush = false
    contentItalic = false

    @event change = function
    @event open = function
    @event close = function
  }

  state openValues = defaultOpen

  functions {
    function itemValue(item, index) {
      return item.value !== undefined && item.value !== ""
        ? String(item.value)
        : String(index)
    }

    function nestedValue(parent, parentIndex, item, index) {
      return itemValue(parent, parentIndex) + "." + itemValue(item, index)
    }

    function isOpen(value) {
      return openValues.includes(value)
    }

    function allowsMultiple() {
      return multiple || alwaysOpen
    }

    function dispatchAccordionEvent(sourceEvent, eventName, value, item, root, customEvent) {
      root = sourceEvent.currentTarget.closest("[data-wrn-accordion]")

      if (!root) {
        return
      }

      customEvent = document.createEvent("CustomEvent")
      customEvent.initCustomEvent(eventName, true, false, {
        component: "Accordion",
        value: value,
        item: item,
        open: isOpen(value),
        openValues: openValues
      })
      root.dispatchEvent(customEvent)
    }

    function toggleItem(sourceEvent, value, item, wasOpen) {
      if (disabled || item.disabled) {
        return
      }

      wasOpen = isOpen(value)

      if (wasOpen) {
        openValues = openValues.filter((entry) => entry !== value)
      } else if (allowsMultiple()) {
        openValues = openValues.concat([value])
      } else {
        openValues = [value]
      }

      dispatchAccordionEvent(
        sourceEvent,
        wasOpen ? "close" : "open",
        value,
        item
      )
      dispatchAccordionEvent(sourceEvent, "change", value, item)
    }

  }

  view {
        <div
      {...attrs}
      id="{id}"
      data-wrn-accordion
      data-variant="{variant}"
      data-indicator="{indicator}"
      data-multiple="{allowsMultiple() ? 'true' : 'false'}"
      class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--accordion wire-next--accordion-{variant} {bordered ? 'wire-next--accordion-bordered' : ''} {separated ? 'wire-next--accordion-separated' : ''} {flush ? 'wire-next--accordion-flush' : ''} {disabled ? 'wire-next--disabled' : ''} {class}"
    >
      {#each items as item, index}
        <section
          class="wire-next__accordion-item"
          data-open="{isOpen(itemValue(item, index)) ? 'true' : 'false'}"
          data-disabled="{disabled || item.disabled ? 'true' : 'false'}"
        >
        <h3 class="wire-next__accordion-heading">
        <button
              type="button"
              id="{id}-trigger-{index}"
              aria-expanded="{isOpen(itemValue(item, index)) ? 'true' : 'false'}"
              aria-controls="{id}-panel-{index}"
              disabled="{disabled || item.disabled}"
              @click="toggleItem(event, itemValue(item, index), item)"
            >
              {#if showIndicator && indicatorPosition === "start"}
                <span class="wire-next__accordion-indicator" aria-hidden="true">
                  {#if indicator === "chevron"}
                    <span class="icon-[lucide--chevron-down]">
        </span>
                  {:else}
                    <span>+</span>
                  {/if}
                </span>
              {/if}
              <span>{item.label || item.title}</span>
              {#if showIndicator && indicatorPosition === "end"}
                <span class="wire-next__accordion-indicator" aria-hidden="true">
                  {#if indicator === "chevron"}
                    <span class="icon-[lucide--chevron-down]">
        </span>
                  {:else}
                    <span>+</span>
                  {/if}
                </span>
              {/if}
            </button>
        </h3>
        <div
            id="{id}-panel-{index}"
            class="wire-next__accordion-panel"
            role="region"
            aria-labelledby="{id}-trigger-{index}"
            aria-hidden="{isOpen(itemValue(item, index)) ? 'false' : 'true'}"
          >
        <div>
        <div class="wire-next__accordion-content {contentItalic ? 'wire-next__accordion-content-italic' : ''}">
                {#if item.content}<p>{item.content}</p>{/if}

                {#if item.children && item.children.length > 0}
                  <div class="wire-next__accordion-nested">
                    {#each item.children as child, childIndex}
                      <section
                        class="wire-next__accordion-item"
                        data-open="{isOpen(nestedValue(item, index, child, childIndex)) ? 'true' : 'false'}"
                      >
        <h4 class="wire-next__accordion-heading">
        <button
                            type="button"
                            id="{id}-trigger-{index}-{childIndex}"
                            aria-expanded="{isOpen(nestedValue(item, index, child, childIndex)) ? 'true' : 'false'}"
                            aria-controls="{id}-panel-{index}-{childIndex}"
                            disabled="{disabled || child.disabled}"
                            @click="toggleItem(event, nestedValue(item, index, child, childIndex), child)"
                          >
                            {#if showIndicator}
                              <span class="wire-next__accordion-indicator" aria-hidden="true">
                                {#if indicator === "chevron"}
                                  <span class="icon-[lucide--chevron-down]">
        </span>
                                {:else}
                                  <span>+</span>
                                {/if}
                              </span>
                            {/if}
                            <span>{child.label || child.title}</span>
        </button>
        </h4>
        <div
                          id="{id}-panel-{index}-{childIndex}"
                          class="wire-next__accordion-panel"
                          role="region"
                          aria-labelledby="{id}-trigger-{index}-{childIndex}"
                          aria-hidden="{isOpen(nestedValue(item, index, child, childIndex)) ? 'false' : 'true'}"
                        >
        <div>
        <div class="wire-next__accordion-content {contentItalic ? 'wire-next__accordion-content-italic' : ''}">
        <p>{child.content}</p>
        </div>
        </div>
        </div>
        </section>
                    {/each}
                  </div>
                {/if}
              </div>
        </div>
        </div>
        </section>
      {/each}
    </div>
    }
}
} }