diff --git a/.changeset/smooth-games-tease.md b/.changeset/smooth-games-tease.md new file mode 100644 index 000000000..7a7b4977d --- /dev/null +++ b/.changeset/smooth-games-tease.md @@ -0,0 +1,7 @@ +--- +"@getflip/swirl-components": minor +"@getflip/swirl-components-angular": minor +"@getflip/swirl-components-react": minor +--- + +Add swirl-accordion and swirl-accordion-item components diff --git a/packages/swirl-components/src/components.d.ts b/packages/swirl-components/src/components.d.ts index c99114c7a..a1075e5da 100644 --- a/packages/swirl-components/src/components.d.ts +++ b/packages/swirl-components/src/components.d.ts @@ -5,6 +5,7 @@ * It contains typing information for all components that exist in this project. */ import { HTMLStencilElement, JSXBase } from "@stencil/core/internal"; +import { SwirlHeadingLevel } from "./components/swirl-heading/swirl-heading"; import { SwirlActionListItemIntent, SwirlActionListItemSize } from "./components/swirl-action-list-item/swirl-action-list-item"; import { SwirlAppLayoutMobileView } from "./components/swirl-app-layout/swirl-app-layout"; import { SwirlAutocompleteSuggestion, SwirlAutocompleteValue } from "./components/swirl-autocomplete/swirl-autocomplete"; @@ -28,7 +29,7 @@ import { SwirlFileViewerPdfViewMode, SwirlFileViewerPdfZoom } from "./components import { SwirlFileViewerPdfViewMode as SwirlFileViewerPdfViewMode1, SwirlFileViewerPdfZoom as SwirlFileViewerPdfZoom1 } from "./components/swirl-file-viewer/viewers/swirl-file-viewer-pdf/swirl-file-viewer-pdf"; import { SwirlFormControlLabelPosition } from "./components/swirl-form-control/swirl-form-control"; import { SwirlFormGroupOrientation } from "./components/swirl-form-group/swirl-form-group"; -import { SwirlHeadingAlign, SwirlHeadingLevel, SwirlHeadingTag } from "./components/swirl-heading/swirl-heading"; +import { SwirlHeadingAlign, SwirlHeadingLevel as SwirlHeadingLevel1, SwirlHeadingTag } from "./components/swirl-heading/swirl-heading"; import { SwirlIconSize } from "./components/swirl-icon/swirl-icon.types"; import { SwirlInlineErrorSize } from "./components/swirl-inline-error/swirl-inline-error"; import { SwirlInlineNotificationAriaRole, SwirlInlineNotificationIntent } from "./components/swirl-inline-notification/swirl-inline-notification"; @@ -63,6 +64,7 @@ import { SwirlToastIntent } from "./components/swirl-toast/swirl-toast"; import { SwirlToastConfig, SwirlToastMessage } from "./components/swirl-toast-provider/swirl-toast-provider"; import { SwirlToolbarOrientation } from "./components/swirl-toolbar/swirl-toolbar"; import { SwirlTooltipPosition } from "./components/swirl-tooltip/swirl-tooltip"; +export { SwirlHeadingLevel } from "./components/swirl-heading/swirl-heading"; export { SwirlActionListItemIntent, SwirlActionListItemSize } from "./components/swirl-action-list-item/swirl-action-list-item"; export { SwirlAppLayoutMobileView } from "./components/swirl-app-layout/swirl-app-layout"; export { SwirlAutocompleteSuggestion, SwirlAutocompleteValue } from "./components/swirl-autocomplete/swirl-autocomplete"; @@ -86,7 +88,7 @@ export { SwirlFileViewerPdfViewMode, SwirlFileViewerPdfZoom } from "./components export { SwirlFileViewerPdfViewMode as SwirlFileViewerPdfViewMode1, SwirlFileViewerPdfZoom as SwirlFileViewerPdfZoom1 } from "./components/swirl-file-viewer/viewers/swirl-file-viewer-pdf/swirl-file-viewer-pdf"; export { SwirlFormControlLabelPosition } from "./components/swirl-form-control/swirl-form-control"; export { SwirlFormGroupOrientation } from "./components/swirl-form-group/swirl-form-group"; -export { SwirlHeadingAlign, SwirlHeadingLevel, SwirlHeadingTag } from "./components/swirl-heading/swirl-heading"; +export { SwirlHeadingAlign, SwirlHeadingLevel as SwirlHeadingLevel1, SwirlHeadingTag } from "./components/swirl-heading/swirl-heading"; export { SwirlIconSize } from "./components/swirl-icon/swirl-icon.types"; export { SwirlInlineErrorSize } from "./components/swirl-inline-error/swirl-inline-error"; export { SwirlInlineNotificationAriaRole, SwirlInlineNotificationIntent } from "./components/swirl-inline-notification/swirl-inline-notification"; @@ -124,6 +126,27 @@ export { SwirlTooltipPosition } from "./components/swirl-tooltip/swirl-tooltip"; export namespace Components { interface FileManager { } + interface SwirlAccordion { + } + interface SwirlAccordionItem { + /** + * Collapsed the accordion item. + */ + "collapse": () => Promise; + "description"?: string; + "disabled"?: boolean; + /** + * Expands the accordion item. + */ + "expand": () => Promise; + "heading": string; + "headingLevel"?: SwirlHeadingLevel; + "initiallyOpen"?: boolean; + /** + * Toggles the accordion item. + */ + "toggle": () => Promise; + } interface SwirlActionList { } interface SwirlActionListItem { @@ -578,7 +601,7 @@ export namespace Components { "as"?: SwirlHeadingTag; "balance"?: boolean; "headingId"?: string; - "level"?: SwirlHeadingLevel; + "level"?: SwirlHeadingLevel1; "lines"?: number; "text": string; "truncate"?: boolean; @@ -1752,6 +1775,10 @@ export namespace Components { interface SwirlVisuallyHidden { } } +export interface SwirlAccordionItemCustomEvent extends CustomEvent { + detail: T; + target: HTMLSwirlAccordionItemElement; +} export interface SwirlAppBarCustomEvent extends CustomEvent { detail: T; target: HTMLSwirlAppBarElement; @@ -1923,6 +1950,18 @@ declare global { prototype: HTMLFileManagerElement; new (): HTMLFileManagerElement; }; + interface HTMLSwirlAccordionElement extends Components.SwirlAccordion, HTMLStencilElement { + } + var HTMLSwirlAccordionElement: { + prototype: HTMLSwirlAccordionElement; + new (): HTMLSwirlAccordionElement; + }; + interface HTMLSwirlAccordionItemElement extends Components.SwirlAccordionItem, HTMLStencilElement { + } + var HTMLSwirlAccordionItemElement: { + prototype: HTMLSwirlAccordionItemElement; + new (): HTMLSwirlAccordionItemElement; + }; interface HTMLSwirlActionListElement extends Components.SwirlActionList, HTMLStencilElement { } var HTMLSwirlActionListElement: { @@ -3725,6 +3764,8 @@ declare global { }; interface HTMLElementTagNameMap { "file-manager": HTMLFileManagerElement; + "swirl-accordion": HTMLSwirlAccordionElement; + "swirl-accordion-item": HTMLSwirlAccordionItemElement; "swirl-action-list": HTMLSwirlActionListElement; "swirl-action-list-item": HTMLSwirlActionListItemElement; "swirl-action-list-section": HTMLSwirlActionListSectionElement; @@ -4029,6 +4070,16 @@ declare global { declare namespace LocalJSX { interface FileManager { } + interface SwirlAccordion { + } + interface SwirlAccordionItem { + "description"?: string; + "disabled"?: boolean; + "heading": string; + "headingLevel"?: SwirlHeadingLevel; + "initiallyOpen"?: boolean; + "onExpansionChange"?: (event: SwirlAccordionItemCustomEvent) => void; + } interface SwirlActionList { } interface SwirlActionListItem { @@ -4431,7 +4482,7 @@ declare namespace LocalJSX { "as"?: SwirlHeadingTag; "balance"?: boolean; "headingId"?: string; - "level"?: SwirlHeadingLevel; + "level"?: SwirlHeadingLevel1; "lines"?: number; "text": string; "truncate"?: boolean; @@ -5523,6 +5574,8 @@ declare namespace LocalJSX { } interface IntrinsicElements { "file-manager": FileManager; + "swirl-accordion": SwirlAccordion; + "swirl-accordion-item": SwirlAccordionItem; "swirl-action-list": SwirlActionList; "swirl-action-list-item": SwirlActionListItem; "swirl-action-list-section": SwirlActionListSection; @@ -5829,6 +5882,8 @@ declare module "@stencil/core" { export namespace JSX { interface IntrinsicElements { "file-manager": LocalJSX.FileManager & JSXBase.HTMLAttributes; + "swirl-accordion": LocalJSX.SwirlAccordion & JSXBase.HTMLAttributes; + "swirl-accordion-item": LocalJSX.SwirlAccordionItem & JSXBase.HTMLAttributes; "swirl-action-list": LocalJSX.SwirlActionList & JSXBase.HTMLAttributes; "swirl-action-list-item": LocalJSX.SwirlActionListItem & JSXBase.HTMLAttributes; "swirl-action-list-section": LocalJSX.SwirlActionListSection & JSXBase.HTMLAttributes; diff --git a/packages/swirl-components/src/components/swirl-accordion-item/swirl-accordion-item.css b/packages/swirl-components/src/components/swirl-accordion-item/swirl-accordion-item.css new file mode 100644 index 000000000..d38db799c --- /dev/null +++ b/packages/swirl-components/src/components/swirl-accordion-item/swirl-accordion-item.css @@ -0,0 +1,142 @@ +@import "../../styles/media-queries.css"; + +:host { + display: block; + + & * { + box-sizing: border-box; + } + + &(:last-child) .accordion-item { + border-bottom: 0.0625rem solid var(--s-border-default); + } +} + +.accordion-item { + display: grid; + align-content: start; + border-top: 0.0625rem solid var(--s-border-default); + transition: grid-template-rows 0.15s; + grid-template-rows: auto 0fr; + will-change: grid-template-rows; + + @media (prefers-reduced-motion) { + transition: none; + } +} + +.accordion-item--expanded { + grid-template-rows: auto 1fr; + + & .accordion-item__content { + padding-top: var(--s-space-16); + padding-bottom: var(--s-space-16); + animation: accordion-overflow 0.01s; + animation-delay: 0.15s; + animation-fill-mode: forwards; + + @media (--from-desktop-without-touch) { + padding-top: var(--s-space-8); + } + } +} + +.accordion-item__heading { + margin: 0; + padding: 0; + font-size: var(--s-font-size-base); + font-weight: var(--s-font-weight-semibold); + + @media (--from-desktop-without-touch) { + font-size: var(--s-font-size-sm); + } +} + +.accordion-item__toggle { + display: flex; + width: 100%; + padding: 0; + padding-top: var(--s-space-16); + padding-bottom: var(--s-space-16); + align-items: center; + border: none; + color: var(--s-text-default); + background-color: var(--s-surface-default); + font: inherit; + line-height: var(--s-line-height-lg); + text-align: start; + cursor: pointer; + gap: var(--s-space-16); + + &:hover { + background-color: var(--s-surface-hovered); + } + + &:active { + background-color: var(--s-surface-pressed); + } + + &:disabled { + color: var(--s-text-disabled); + background-color: var(--s-surface-default); + cursor: default; + + & .text { + color: var(--s-text-disabled); + } + } + + &:focus:not(:focus-visible) { + outline: none; + } + + &:focus-visible { + outline-color: var(--s-focus-default); + } + + @media (--from-desktop-without-touch) { + line-height: var(--s-line-height-sm); + } +} + +.accordion-item__toggle-text { + flex-grow: 1; +} + +.accordion-item__icon { + display: inline-flex; + padding-right: var(--s-space-8); + flex-shrink: 0; +} + +.accordion-item__content { + overflow: hidden; + padding-top: 0; + padding-bottom: 0; + transition: padding-top 0.15s, padding-bottom 0.15s; + animation: accordion-display 0.01s; + animation-delay: 0.15s; + animation-fill-mode: forwards; + + @media (prefers-reduced-motion) { + transition: none; + } +} + +@keyframes accordion-overflow { + from { + overflow: hidden; + } + to { + overflow: visible; + } +} + +@keyframes accordion-display { + from { + display: block; + } + to { + display: none; + } +} diff --git a/packages/swirl-components/src/components/swirl-accordion-item/swirl-accordion-item.mdx b/packages/swirl-components/src/components/swirl-accordion-item/swirl-accordion-item.mdx new file mode 100644 index 000000000..7ba0c9604 --- /dev/null +++ b/packages/swirl-components/src/components/swirl-accordion-item/swirl-accordion-item.mdx @@ -0,0 +1,30 @@ +import { Controls, Canvas, Meta, Story } from "@storybook/addon-docs"; +import * as Stories from "./swirl-accordion-item.stories"; + + + +# SwirlAccordionItem + +The SwirlAccordionItem component is used to provide a collapsible section of +content. It is always contained within a +[SwirlAccordion](?path=/docs/components-swirlaccordion--docs) component. + +- **[Figma Design Specs](https://www.figma.com/file/iRHiAFsHxnzux8gQ9EjlZe/Swirl-Components?node-id=5488%3A32622&mode=dev)** +- **[Source Code](https://github.com/flip-corp/swirl/tree/main/packages/swirl-components/src/components/swirl-accordion-item)** + +## Usage + + + + + +## Theming + +| Variable | Usage | +| -------------------- | ----------------- | +| `--s-border-default` | Separator color | +| `--s-text-subdued` | Description color | + +## Accessibility + +See [SwirlAccordion](?path=/docs/components-swirlaccordion--docs). diff --git a/packages/swirl-components/src/components/swirl-accordion-item/swirl-accordion-item.spec.tsx b/packages/swirl-components/src/components/swirl-accordion-item/swirl-accordion-item.spec.tsx new file mode 100644 index 000000000..8e32e86de --- /dev/null +++ b/packages/swirl-components/src/components/swirl-accordion-item/swirl-accordion-item.spec.tsx @@ -0,0 +1,116 @@ +import { newSpecPage } from "@stencil/core/testing"; + +import { SwirlAccordionItem } from "./swirl-accordion-item"; + +describe("swirl-accordion-item", () => { + it("renders heading and description", async () => { + const page = await newSpecPage({ + components: [SwirlAccordionItem], + html: ` + + + Content + + + `, + }); + + const id = page.root.shadowRoot.querySelector( + ".accordion-item__content" + ).id; + + expect(page.root).toEqualHtml(` + + +
+

+ +

+
+ +
+
+
+ Content +
+ `); + }); + + it("can be toggled", async () => { + const page = await newSpecPage({ + components: [SwirlAccordionItem], + html: ` + + + Content + + + `, + }); + + expect( + page.root.shadowRoot + .querySelector(".accordion-item") + .classList.contains("accordion-item--expanded") + ).toBe(false); + + const toggle = page.root.shadowRoot.querySelector( + ".accordion-item__toggle" + ); + + toggle.click(); + await page.waitForChanges(); + + expect( + page.root.shadowRoot + .querySelector(".accordion-item") + .classList.contains("accordion-item--expanded") + ).toBe(true); + + toggle.click(); + await page.waitForChanges(); + + expect( + page.root.shadowRoot + .querySelector(".accordion-item") + .classList.contains("accordion-item--expanded") + ).toBe(false); + }); + + it("fires expansionChange events", async () => { + const page = await newSpecPage({ + components: [SwirlAccordionItem], + html: ` + + + Content + + + `, + }); + + const spy = jest.fn(); + + page.root.addEventListener("expansionChange", spy); + + const toggle = page.root.shadowRoot.querySelector( + ".accordion-item__toggle" + ); + + toggle.click(); + expect(spy.mock.calls[0][0].detail).toBe(true); + + toggle.click(); + expect(spy.mock.calls[1][0].detail).toBe(false); + }); +}); diff --git a/packages/swirl-components/src/components/swirl-accordion-item/swirl-accordion-item.stories.ts b/packages/swirl-components/src/components/swirl-accordion-item/swirl-accordion-item.stories.ts new file mode 100644 index 000000000..3be69fa30 --- /dev/null +++ b/packages/swirl-components/src/components/swirl-accordion-item/swirl-accordion-item.stories.ts @@ -0,0 +1,41 @@ +import { generateStoryElement } from "../../utils"; +import Docs from "./swirl-accordion-item.mdx"; + +export default { + component: "swirl-accordion-item", + tags: ["autodocs"], + parameters: { + docs: { + page: Docs, + }, + }, + title: "Components/SwirlAccordionItem", +}; + +const Template = (args) => { + const container = document.createElement("swirl-accordion"); + const element = generateStoryElement("swirl-accordion-item", args); + + element.innerHTML = ` + + + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed euismod + euismod libero, nec sollicitudin diam ultricies ut. Donec eget + consectetur libero. Donec et mi non mauris fermentum dictum. Sed + scelerisque, sapien vitae fringilla aliquam, quam sapien aliquet. + + + + `; + + container.append("\n ", element, "\n"); + + return container; +}; + +export const SwirlAccordionItem = Template.bind({}); + +SwirlAccordionItem.args = { + description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.", + heading: "Accordion item", +}; diff --git a/packages/swirl-components/src/components/swirl-accordion-item/swirl-accordion-item.tsx b/packages/swirl-components/src/components/swirl-accordion-item/swirl-accordion-item.tsx new file mode 100644 index 000000000..dce16cdda --- /dev/null +++ b/packages/swirl-components/src/components/swirl-accordion-item/swirl-accordion-item.tsx @@ -0,0 +1,141 @@ +import { + Component, + Element, + Event, + EventEmitter, + h, + Host, + Method, + Prop, + State, +} from "@stencil/core"; +import { v4 as uuid } from "uuid"; +import { SwirlHeadingLevel } from "../swirl-heading/swirl-heading"; +import classnames from "classnames"; + +@Component({ + shadow: true, + styleUrl: "swirl-accordion-item.css", + tag: "swirl-accordion-item", +}) +export class SwirlAccordionItem { + @Element() el: HTMLElement; + + @Prop() description?: string; + @Prop() disabled?: boolean; + @Prop() heading!: string; + @Prop() headingLevel?: SwirlHeadingLevel = 2; + @Prop() initiallyOpen?: boolean; + + @Event() expansionChange: EventEmitter; + + @State() expanded = false; + + private accordion: HTMLSwirlAccordionElement; + private id = `accordion-item-${uuid()}`; + private headingId = `${this.id}-heading`; + + componentWillLoad() { + this.accordion = this.el.closest("swirl-accordion"); + this.expanded = (this.initiallyOpen && !this.disabled) || false; + + if (!Boolean(this.accordion)) { + throw new Error( + "[Swirl] swirl-accordion-item must be a child of a swirl-accordion." + ); + } + } + + /** + * Collapsed the accordion item. + */ + @Method() + async collapse() { + if (!this.expanded || this.disabled) { + return; + } + + this.expanded = false; + this.expansionChange.emit(this.expanded); + } + + /** + * Expands the accordion item. + */ + @Method() + async expand() { + if (this.expanded || this.disabled) { + return; + } + + this.expanded = true; + this.expansionChange.emit(this.expanded); + } + + /** + * Toggles the accordion item. + */ + @Method() + async toggle() { + if (this.expanded) { + this.collapse(); + } else { + this.expand(); + } + } + + private onHeadingClick = () => { + this.toggle(); + }; + + render() { + const HeadingTag = `h${this.headingLevel}`; + + const className = classnames("accordion-item", { + "accordion-item--expanded": this.expanded, + }); + + return ( + +
+ + + +
+ +
+
+
+ ); + } +} diff --git a/packages/swirl-components/src/components/swirl-accordion/swirl-accordion.css b/packages/swirl-components/src/components/swirl-accordion/swirl-accordion.css new file mode 100644 index 000000000..b0e5b62a6 --- /dev/null +++ b/packages/swirl-components/src/components/swirl-accordion/swirl-accordion.css @@ -0,0 +1,7 @@ +:host { + display: block; + + & * { + box-sizing: border-box; + } +} diff --git a/packages/swirl-components/src/components/swirl-accordion/swirl-accordion.mdx b/packages/swirl-components/src/components/swirl-accordion/swirl-accordion.mdx new file mode 100644 index 000000000..d400210c7 --- /dev/null +++ b/packages/swirl-components/src/components/swirl-accordion/swirl-accordion.mdx @@ -0,0 +1,41 @@ +import { Controls, Canvas, Meta, Story } from "@storybook/addon-docs"; +import * as Stories from "./swirl-accordion.stories"; + + + +# SwirlAccordion + +The SwirlAccordion component is used to display vertically stacked headings that +reveal sections of content. + +- **[Figma Design Specs](https://www.figma.com/file/iRHiAFsHxnzux8gQ9EjlZe/Swirl-Components?type=design&node-id=5714-64436&mode=design&t=GYMMqk4QYgML1o2g-0)** +- **[Source Code](https://github.com/flip-corp/swirl/tree/main/packages/swirl-components/src/components/swirl-accordion)** + +## Usage + + + + + +## Accessibility + +### ARIA + +The component follows the +[WAI-ARIA Accordion Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/accordion/). + +| ARIA | Usage | +| ---------------- | ------------------------------------------------------------------------- | +| `role="heading"` | Used to provide a heading for each accordion item. | +| `role="button"` | Contained by the headline of each accordion item. | +| `aria-expanded` | Set on the item's button. | +| `aria-controls` | Set on the item's button, referencing the controlled content section. | +| `role="region"` | Set on each content section container. | +| `labelledby` | Set on each content section container, referencing the associated button. | + +### Keyboard + +| Key | Action | +| ---------------- | -------------------------------------------------------------- | +| ENTER | Activates the focused accordion item, closing all other items. | +| SPACE | Activates the focused accordion item, closing all other items. | diff --git a/packages/swirl-components/src/components/swirl-accordion/swirl-accordion.spec.tsx b/packages/swirl-components/src/components/swirl-accordion/swirl-accordion.spec.tsx new file mode 100644 index 000000000..b73b125eb --- /dev/null +++ b/packages/swirl-components/src/components/swirl-accordion/swirl-accordion.spec.tsx @@ -0,0 +1,91 @@ +import { newSpecPage } from "@stencil/core/testing"; + +import { SwirlAccordion } from "./swirl-accordion"; +import { SwirlAccordionItem } from "../swirl-accordion-item/swirl-accordion-item"; + +describe("swirl-accordion", () => { + it("renders its items", async () => { + const page = await newSpecPage({ + components: [SwirlAccordion], + html: ` + + + Content 1 + + + Content 2 + + + Content 3 + + + `, + }); + + expect(page.root).toEqualHtml(` + + + + + + Content 1 + + + Content 2 + + + Content 3 + + + `); + }); + + it("renders only allows one expanded item", async () => { + const page = await newSpecPage({ + components: [SwirlAccordion, SwirlAccordionItem], + html: ` + + + Content 1 + + + Content 2 + + + Content 3 + + + `, + }); + + const items = page.root.querySelectorAll("swirl-accordion-item"); + + expect( + items[0].shadowRoot.querySelector(".accordion-item--expanded") + ).toBeTruthy(); + expect( + items[1].shadowRoot.querySelector(".accordion-item--expanded") + ).toBeFalsy(); + expect( + items[2].shadowRoot.querySelector(".accordion-item--expanded") + ).toBeFalsy(); + + const secondItemToggle = + items[1].shadowRoot.querySelector( + ".accordion-item__toggle" + ); + + secondItemToggle.click(); + await page.waitForChanges(); + + expect( + items[0].shadowRoot.querySelector(".accordion-item--expanded") + ).toBeFalsy(); + expect( + items[1].shadowRoot.querySelector(".accordion-item--expanded") + ).toBeTruthy(); + expect( + items[2].shadowRoot.querySelector(".accordion-item--expanded") + ).toBeFalsy(); + }); +}); diff --git a/packages/swirl-components/src/components/swirl-accordion/swirl-accordion.stories.ts b/packages/swirl-components/src/components/swirl-accordion/swirl-accordion.stories.ts new file mode 100644 index 000000000..1df63153a --- /dev/null +++ b/packages/swirl-components/src/components/swirl-accordion/swirl-accordion.stories.ts @@ -0,0 +1,70 @@ +import { generateStoryElement } from "../../utils"; +import Docs from "./swirl-accordion.mdx"; + +export default { + component: "swirl-accordion", + tags: ["autodocs"], + parameters: { + docs: { + page: Docs, + }, + }, + title: "Components/SwirlAccordion", +}; + +const Template = (args) => { + const element = generateStoryElement("swirl-accordion", args); + + element.innerHTML = ` + + + + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed euismod + euismod libero, nec sollicitudin diam ultricies ut. Donec eget + consectetur libero. Donec et mi non mauris fermentum dictum. Sed + scelerisque, sapien vitae fringilla aliquam, quam sapien aliquet. + + + + + + + + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed euismod + euismod libero, nec sollicitudin diam ultricies ut. Donec eget + consectetur libero. Donec et mi non mauris fermentum dictum. Sed + scelerisque, sapien vitae fringilla aliquam, quam sapien aliquet. + + + + + + + + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed euismod + euismod libero, nec sollicitudin diam ultricies ut. Donec eget + consectetur libero. Donec et mi non mauris fermentum dictum. Sed + scelerisque, sapien vitae fringilla aliquam, quam sapien aliquet. + + + + + + + + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed euismod + euismod libero, nec sollicitudin diam ultricies ut. Donec eget + consectetur libero. Donec et mi non mauris fermentum dictum. Sed + scelerisque, sapien vitae fringilla aliquam, quam sapien aliquet. + + + + +`; + + return element; +}; + +export const SwirlAccordion = Template.bind({}); + +SwirlAccordion.args = {}; diff --git a/packages/swirl-components/src/components/swirl-accordion/swirl-accordion.tsx b/packages/swirl-components/src/components/swirl-accordion/swirl-accordion.tsx new file mode 100644 index 000000000..d75ff9adb --- /dev/null +++ b/packages/swirl-components/src/components/swirl-accordion/swirl-accordion.tsx @@ -0,0 +1,41 @@ +import { Component, Element, h, Host } from "@stencil/core"; + +@Component({ + shadow: true, + styleUrl: "swirl-accordion.css", + tag: "swirl-accordion", +}) +export class SwirlAccordion { + @Element() el: HTMLElement; + + componentDidLoad() { + this.el.addEventListener( + "expansionChange", + (event: CustomEvent) => { + if (event.detail) { + this.collapseInactiveItems( + event.target as HTMLSwirlAccordionItemElement + ); + } + } + ); + } + + private collapseInactiveItems(activeItem: HTMLSwirlAccordionItemElement) { + Array.from(this.el.querySelectorAll("swirl-accordion-item")).forEach( + (item) => { + if (item !== activeItem) { + item.collapse(); + } + } + ); + } + + render() { + return ( + + + + ); + } +} diff --git a/packages/swirl-components/vscode-data.json b/packages/swirl-components/vscode-data.json index 69345d60f..c465085cc 100644 --- a/packages/swirl-components/vscode-data.json +++ b/packages/swirl-components/vscode-data.json @@ -9,6 +9,43 @@ }, "attributes": [] }, + { + "name": "swirl-accordion", + "description": { + "kind": "markdown", + "value": "" + }, + "attributes": [] + }, + { + "name": "swirl-accordion-item", + "description": { + "kind": "markdown", + "value": "" + }, + "attributes": [ + { + "name": "description", + "description": "" + }, + { + "name": "disabled", + "description": "" + }, + { + "name": "heading", + "description": "" + }, + { + "name": "heading-level", + "description": "" + }, + { + "name": "initially-open", + "description": "" + } + ] + }, { "name": "swirl-action-list", "description": {