[//]: # 'AUTO INSERT HEADER PREPUBLISH'
# Accordion
`lion-accordion` is a component used to toggle the display of sections of content.
Its purpose is to reduce the need to scroll when presenting multiple sections of content on a single page. Accordions often allow users to get the big picture before focusing on details.
```js script
import { LitElement } from '@lion/core';
import { html } from '@lion/core';
import './lion-accordion.js';
export default {
title: 'Navigation/Accordion',
};
```
```js preview-story
export const main = () => html`
Lorem ipsum dolor sit, amet consectetur adipisicing elit.
Laboriosam sequi odit cumque, enim aut assumenda itaque quis voluptas est quos fugiat unde
labore reiciendis saepe, iure, optio officiis obcaecati quibusdam.
`;
```
## How to use
### Installation
```bash
npm i --save @lion/accordion
```
```js
import { LionAccordion } from '@lion/accordion';
// or
import '@lion/accordion/lion-accordion.js';
```
### Usage
```html
Lorem ipsum dolor sit, amet consectetur adipisicing elit.
Laboriosam sequi odit cumque, enim aut assumenda itaque quis voluptas est quos fugiat unde
labore reiciendis saepe, iure, optio officiis obcaecati quibusdam.
```
> An accordion exists off a list of expandable headings (of the same level). To get this behavior you need to add a slot="invoker" to the heading and place a button as the content.
## Examples
### Expanded
You can set `expanded` to pre-expand a certain invoker.
```js preview-story
export const expanded = () => html`
Lorem ipsum dolor sit, amet consectetur adipisicing elit.
Laboriosam sequi odit cumque, enim aut assumenda itaque quis voluptas est quos fugiat unde
labore reiciendis saepe, iure, optio officiis obcaecati quibusdam.
`;
```
### Slots Order
The invoker and content slots are ordered by DOM order.
This means you must locate your content before it's invoker.
```js preview-story
export const slotsOrder = () => html`
Lorem ipsum dolor sit, amet consectetur adipisicing elit.
Laboriosam sequi odit cumque, enim aut assumenda itaque quis voluptas est quos fugiat unde
labore reiciendis saepe, iure, optio officiis obcaecati quibusdam.
`;
```
### Distribute New Elements
Below, we demonstrate on how you could dynamically add new invoker + content.
```js preview-story
export const distributeNewElement = () => {
const tagName = 'demo-accordion-add-dynamically';
if (!customElements.get(tagName)) {
customElements.define(
tagName,
class extends LitElement {
static get properties() {
return {
__collection: { type: Array },
};
}
render() {
return html`
Append
content 1
content 2
Push
content 1
content 2
${this.__collection.map(
item => html`
${item.content}
`,
)}
`;
}
constructor() {
super();
this.__collection = [];
}
__handleAppendClick() {
const accordionElement = this.shadowRoot.querySelector('#appendAccordion');
const c = 2;
const n = Math.floor(accordionElement.children.length / 2);
for (let i = n + 1; i < n + c; i += 1) {
const invoker = document.createElement('h4');
const button = document.createElement('button');
button.innerText = `header ${i}`;
invoker.setAttribute('slot', 'invoker');
invoker.appendChild(button);
const content = document.createElement('p');
content.setAttribute('slot', 'content');
content.innerText = `content ${i}`;
accordionElement.append(invoker);
accordionElement.append(content);
}
}
__handlePushClick() {
const accordionElement = this.shadowRoot.querySelector('#pushTabs');
const i = Math.floor(accordionElement.children.length / 2) + 1;
this.__collection = [
...this.__collection,
{
invoker: `header ${i}`,
content: `content ${i}`,
},
];
}
},
);
}
return html` `;
};
```
One way is by creating the DOM elements and appending them as needed.
Inside your `lion-accordion` extension, an example for appending nodes on a certain button click:
```js
__handleAppendClick() {
const accordionAmount = this.children.length / 2;
const invoker = document.createElement('h4');
const button = document.createElement('button');
button.innerText = `header ${accordionAmount + 1}`;
invoker.setAttribute('slot', 'invoker');
invoker.appendChild(button);
const content = document.createElement('p');
content.setAttribute('slot', 'content');
content.innerText = `content ${accordionAmount + 1}`;
this.append(invoker);
this.append(content);
}
```
The other way is by adding data to a Lit property where you loop over this property in your template.
You then need to ensure this causes a re-render.
```js
__handlePushClick() {
const accordionAmount = this.children.length;
myCollection = [
...myCollection,
{
invoker: `header ${accordionAmount + 1}`,
content: `content ${accordionAmount + 1}`,
},
];
renderMyCollection();
}
```
Make sure your template re-renders when myCollection is updated.
```html
${myCollection.map(item => html`
${item.content}
`)}
```
## Rationale
### Contents are not focusable
Focusable elements should have a means to interact with them. Contents themselves do not offer any interactiveness.
If there is a button or a form inside the tab panel then these elements get focused directly.