diff --git a/.changeset/wild-bees-laugh.md b/.changeset/wild-bees-laugh.md new file mode 100644 index 0000000000..25c1d126a9 --- /dev/null +++ b/.changeset/wild-bees-laugh.md @@ -0,0 +1,8 @@ +--- +'@swisspost/design-system-documentation': minor +'@swisspost/design-system-components': minor +'@swisspost/design-system-components-angular': minor +'@swisspost/design-system-components-react': minor +--- + +Added a `post-collapsible-trigger` component to properly handle the role, ARIA attributes, and event listeners for elements that toggle a `post-collapsible`. diff --git a/packages/components-angular/projects/consumer-app/src/app/routes/home/home.component.html b/packages/components-angular/projects/consumer-app/src/app/routes/home/home.component.html index 58327c6fe3..57d7de325f 100644 --- a/packages/components-angular/projects/consumer-app/src/app/routes/home/home.component.html +++ b/packages/components-angular/projects/consumer-app/src/app/routes/home/home.component.html @@ -30,7 +30,10 @@

Post Card-Control

Post Collapsible

- + + + +

Contentus momentus vero siteos et accusam iretea et justo.

diff --git a/packages/components/cypress/e2e/accordion.cy.ts b/packages/components/cypress/e2e/accordion.cy.ts index 9d8bdd7cc7..372947bb72 100644 --- a/packages/components/cypress/e2e/accordion.cy.ts +++ b/packages/components/cypress/e2e/accordion.cy.ts @@ -16,17 +16,17 @@ describe('accordion', () => { }); it('should only show the first element as expanded', () => { - cy.get('@collapsibles').first().find('.collapse').should('be.visible'); + cy.get('@collapsibles').first().shadow().find('post-collapsible').should('be.visible'); }); it('should show the last element as expanded after clicking it', () => { cy.get('@collapsibles').last().click(); - cy.get('@collapsibles').last().find('.collapse').should('be.visible'); + cy.get('@collapsibles').last().shadow().find('post-collapsible').should('be.visible'); }); it('should not show the first element as expanded after clicking the last element', () => { cy.get('@collapsibles').last().click(); - cy.get('@collapsibles').first().find('.collapse').should('be.hidden'); + cy.get('@collapsibles').first().shadow().find('post-collapsible').should('be.hidden'); }); it('should propagate "postToggle" event from post-accordion-item on post-accordion', () => { @@ -73,12 +73,12 @@ describe('accordion', () => { it('should show the last element as expanded after clicking it', () => { cy.get('@collapsibles').last().click(); - cy.get('@collapsibles').last().find('.collapse').should('be.visible'); + cy.get('@collapsibles').last().shadow().find('post-collapsible').should('be.visible'); }); it('should still show the first element as expanded after clicking the last element', () => { cy.get('@collapsibles').last().click(); - cy.get('@collapsibles').first().find('.collapse').should('be.visible'); + cy.get('@collapsibles').first().shadow().find('post-collapsible').should('be.visible'); }); }); }); diff --git a/packages/components/cypress/e2e/collapsible.cy.ts b/packages/components/cypress/e2e/collapsible.cy.ts index f7d2d8a5b7..5ae83f7e9a 100644 --- a/packages/components/cypress/e2e/collapsible.cy.ts +++ b/packages/components/cypress/e2e/collapsible.cy.ts @@ -3,57 +3,75 @@ const COLLAPSIBLE_ID = '6a91848c-16ec-4a23-bc45-51c797b5b2c3'; describe('collapsible', () => { describe('default', () => { beforeEach(() => { - cy.getComponent('collapsible', COLLAPSIBLE_ID); - cy.get('@collapsible').find('.collapse').as('collapse'); - cy.get(`#button--${COLLAPSIBLE_ID}--default`).as('toggler'); + cy.getComponents(COLLAPSIBLE_ID, 'default', 'post-collapsible', 'post-collapsible-trigger'); + cy.get('@collapsible-trigger').find('.btn').as('trigger'); }); - it('should render', () => { + it('should have a collapsible', () => { cy.get('@collapsible').should('exist'); }); - it('should have a collapse', () => { - cy.get('@collapse').should('exist'); + it('should have a trigger', () => { + cy.get('@trigger').should('exist'); }); - it('should have a toggle button', () => { - cy.get('@toggler').should('exist'); + it('should show the collapsible', () => { + cy.get('@collapsible').should(`be.visible`); }); - it('should be expanded', () => { - cy.get('@collapse').should(`be.visible`); + it('should set the correct ARIA attribute on the trigger', () => { + cy.get('@collapsible') + .invoke('attr', 'id') + .then(collapsibleId => { + cy.get('@trigger').should('have.attr', 'aria-controls', collapsibleId); + }); + cy.get('@trigger').should('have.attr', 'aria-expanded', 'true'); }); - it('should be collapsed after clicking on the toggle button once', () => { - cy.get('@toggler').click(); - cy.get('@collapse').should(`be.hidden`); + it('should hide the collapsible after clicking on the trigger once', () => { + cy.get('@trigger').click(); + cy.get('@collapsible').should(`be.hidden`); }); - it('should be expanded after clicking on the toggle button twice', () => { - cy.get('@toggler').dblclick(); - cy.get('@collapse').should(`be.visible`); + it('should update the "aria-expanded" attribute after hiding the collapsible', () => { + cy.get('@trigger').click(); + cy.get('@trigger').should('have.attr', 'aria-expanded', 'false'); + }); + + it('should show the collapsible after clicking on the trigger twice', () => { + cy.get('@trigger').dblclick(); + cy.get('@collapsible').should(`be.visible`); + }); + + it('should update the "aria-expanded" attribute after showing the collapsible', () => { + cy.get('@trigger').dblclick(); + cy.get('@trigger').should('have.attr', 'aria-expanded', 'true'); }); }); describe('initially collapsed', () => { beforeEach(() => { - cy.getComponent('collapsible', COLLAPSIBLE_ID, 'initially-collapsed'); - cy.get('@collapsible').find('.collapse').as('collapse'); - cy.get(`#button--${COLLAPSIBLE_ID}--initially-collapsed`).as('toggler'); + cy.getComponents( + COLLAPSIBLE_ID, + 'initially-collapsed', + 'post-collapsible', + 'post-collapsible-trigger', + ); + cy.get('@collapsible-trigger').find('.btn').as('trigger'); }); - it('should be collapsed', () => { - cy.get('@collapse').should(`be.hidden`); + it('should hide the collapsible', () => { + cy.get('@collapsible').should(`be.hidden`); }); - it('should be expanded after clicking on the toggle button once', () => { - cy.get('@toggler').click(); - cy.get('@collapse').should(`be.visible`); + it('should show the collapsible after clicking on the trigger once', () => { + cy.get('@trigger').click(); + cy.get('@collapsible').should(`be.visible`); }); - it('should be collapsed after clicking on the toggle button twice', () => { - cy.get('@toggler').dblclick(); - cy.get('@collapse').should(`be.hidden`); + it('should hide the collapsible after clicking on the trigger twice', () => { + cy.get('@trigger').dblclick(); + cy.get('@collapsible').should(`be.hidden`); }); }); }); diff --git a/packages/components/cypress/support/commands.ts b/packages/components/cypress/support/commands.ts index 29e8f58b8c..20f89a741e 100644 --- a/packages/components/cypress/support/commands.ts +++ b/packages/components/cypress/support/commands.ts @@ -49,10 +49,16 @@ export const isInViewport = function (_chai: Chai.ChaiStatic) { chai.use(isInViewport); Cypress.Commands.add('getComponent', (component: string, id: string, story = 'default') => { + cy.getComponents(id, story, component); +}); + +Cypress.Commands.add('getComponents', (id: string, story: string, ...components: string[]) => { cy.visit(`/iframe.html?id=${id}--${story}`); - const alias = component.replace(/^post-/, ''); - cy.get(`post-${alias}`, { timeout: 30000 }).as(alias); + components.forEach(component => { + const alias = component.replace(/^post-/, ''); + cy.get(`post-${alias}.hydrated`, { timeout: 30000 }).as(alias); + }); cy.injectAxe(); }); diff --git a/packages/components/cypress/support/index.d.ts b/packages/components/cypress/support/index.d.ts index d7b6a2cd32..96fbc79c05 100644 --- a/packages/components/cypress/support/index.d.ts +++ b/packages/components/cypress/support/index.d.ts @@ -2,6 +2,7 @@ declare global { namespace Cypress { interface Chainable { getComponent(component: string, id: string, story?: string): Chainable; + getComponents(id: string, story: string, ...component: string[]): Chainable; getSnapshots(component: string): Chainable; checkAriaExpanded( controlledElementSelector: string, diff --git a/packages/components/src/components.d.ts b/packages/components/src/components.d.ts index c4a14afe4f..afd3aef23b 100644 --- a/packages/components/src/components.d.ts +++ b/packages/components/src/components.d.ts @@ -134,6 +134,16 @@ export namespace Components { */ "toggle": (open?: boolean) => Promise; } + interface PostCollapsibleTrigger { + /** + * Link the trigger to a post-collapsible with this id + */ + "for": string; + /** + * Update the "aria-controls" and "aria-expanded" attributes on the trigger button + */ + "update": () => Promise; + } /** * @class PostIcon - representing a stencil component */ @@ -393,6 +403,12 @@ declare global { prototype: HTMLPostCollapsibleElement; new (): HTMLPostCollapsibleElement; }; + interface HTMLPostCollapsibleTriggerElement extends Components.PostCollapsibleTrigger, HTMLStencilElement { + } + var HTMLPostCollapsibleTriggerElement: { + prototype: HTMLPostCollapsibleTriggerElement; + new (): HTMLPostCollapsibleTriggerElement; + }; /** * @class PostIcon - representing a stencil component */ @@ -490,6 +506,7 @@ declare global { "post-alert": HTMLPostAlertElement; "post-card-control": HTMLPostCardControlElement; "post-collapsible": HTMLPostCollapsibleElement; + "post-collapsible-trigger": HTMLPostCollapsibleTriggerElement; "post-icon": HTMLPostIconElement; "post-popover": HTMLPostPopoverElement; "post-popovercontainer": HTMLPostPopovercontainerElement; @@ -608,6 +625,12 @@ declare namespace LocalJSX { */ "onPostToggle"?: (event: PostCollapsibleCustomEvent) => void; } + interface PostCollapsibleTrigger { + /** + * Link the trigger to a post-collapsible with this id + */ + "for"?: string; + } /** * @class PostIcon - representing a stencil component */ @@ -747,6 +770,7 @@ declare namespace LocalJSX { "post-alert": PostAlert; "post-card-control": PostCardControl; "post-collapsible": PostCollapsible; + "post-collapsible-trigger": PostCollapsibleTrigger; "post-icon": PostIcon; "post-popover": PostPopover; "post-popovercontainer": PostPopovercontainer; @@ -770,6 +794,7 @@ declare module "@stencil/core" { */ "post-card-control": LocalJSX.PostCardControl & JSXBase.HTMLAttributes; "post-collapsible": LocalJSX.PostCollapsible & JSXBase.HTMLAttributes; + "post-collapsible-trigger": LocalJSX.PostCollapsibleTrigger & JSXBase.HTMLAttributes; /** * @class PostIcon - representing a stencil component */ diff --git a/packages/components/src/components/post-collapsible-trigger/post-collapsible-trigger.tsx b/packages/components/src/components/post-collapsible-trigger/post-collapsible-trigger.tsx new file mode 100644 index 0000000000..9c049ae06d --- /dev/null +++ b/packages/components/src/components/post-collapsible-trigger/post-collapsible-trigger.tsx @@ -0,0 +1,119 @@ +import { Component, Element, Listen, Method, Prop, Watch } from '@stencil/core'; +import { version } from 'typescript'; +import { checkNonEmpty, checkType, debounce } from '@/utils'; +import { PostCollapsibleCustomEvent } from '@/components'; + +@Component({ + tag: 'post-collapsible-trigger', +}) +export class PostCollapsibleTrigger { + private trigger?: HTMLButtonElement; + private observer = new MutationObserver(() => this.setTrigger()); + + @Element() host: HTMLPostCollapsibleTriggerElement; + + /** + * Link the trigger to a post-collapsible with this id + */ + @Prop() for: string; + + /** + * Set the "aria-controls" and "aria-expanded" attributes on the trigger to match the state of the controlled post-collapsible + */ + @Watch('for') + setAriaAttributes() { + checkNonEmpty(this.for, 'The post-collapsible-trigger "for" prop is required.'); + checkType(this.for, 'string', 'The post-collapsible-trigger "for" prop should be a id.'); + + void this.update(); + } + + /** + * Initiate a mutation observer that updates the trigger whenever necessary + */ + connectedCallback() { + this.observer.observe(this.host, { childList: true, subtree: true }); + } + + /** + * Add the "data-version" to the host element and set the trigger + */ + componentDidLoad() { + this.host.setAttribute('data-version', version); + this.setTrigger(); + + if (!this.trigger) console.warn('The post-collapsible-trigger must contain a button.'); + } + + /** + * Disconnect the mutation observer + */ + disconnectedCallback() { + this.observer.disconnect(); + } + + /** + * Update the "aria-expanded" attribute on the trigger anytime the controlled post-collapsible is toggled + */ + @Listen('postToggle', { target: 'document' }) + setAriaExpanded(e: PostCollapsibleCustomEvent) { + if (!this.trigger || !e.target.isEqualNode(this.collapsible)) return; + this.trigger.setAttribute('aria-expanded', `${e.detail}`); + } + + /** + * Update the "aria-controls" and "aria-expanded" attributes on the trigger button + */ + @Method() + async update() { + this.debouncedUpdate(); + } + + private debouncedUpdate = debounce(() => { + if (!this.trigger) return; + + // add the provided id to the aria-controls list + const ariaControls = this.trigger.getAttribute('aria-controls'); + if (!ariaControls?.includes(this.for)) { + const newAriaControls = ariaControls ? `${ariaControls} ${this.for}` : this.for; + this.trigger.setAttribute('aria-controls', newAriaControls); + } + + // set the aria-expanded to `false` if the controlled collapsible is collapsed or undefined, set it to `true` otherwise + const isCollapsed = this.collapsible?.collapsed; + const newAriaExpanded = isCollapsed !== undefined ? !isCollapsed : undefined; + this.trigger.setAttribute('aria-expanded', `${newAriaExpanded}`); + }); + + /** + * Toggle the post-collapsible controlled by the trigger + */ + private async toggleCollapsible() { + await this.collapsible?.toggle(); + } + + /** + * Retrieve the post-collapsible controlled by the trigger + */ + private get collapsible(): HTMLPostCollapsibleElement | null { + const ref = document.getElementById(this.for); + if (ref && ref.localName === 'post-collapsible') { + return ref as HTMLPostCollapsibleElement; + } + + return null; + } + + /** + * Find the button and add the proper event listener and ARIA attributes to it + */ + private setTrigger() { + const trigger = this.host.querySelector('button'); + if (!trigger || (this.trigger && trigger.isEqualNode(this.trigger))) return; + + this.trigger = trigger; + + this.trigger.addEventListener('click', () => this.toggleCollapsible()); + this.setAriaAttributes(); + } +} diff --git a/packages/components/src/components/post-collapsible-trigger/readme.md b/packages/components/src/components/post-collapsible-trigger/readme.md new file mode 100644 index 0000000000..3c5f16db95 --- /dev/null +++ b/packages/components/src/components/post-collapsible-trigger/readme.md @@ -0,0 +1,30 @@ +# post-collapsible-trigger + + + + + + +## Properties + +| Property | Attribute | Description | Type | Default | +| -------- | --------- | --------------------------------------------------- | -------- | ----------- | +| `for` | `for` | Link the trigger to a post-collapsible with this id | `string` | `undefined` | + + +## Methods + +### `update() => Promise` + +Update the "aria-controls" and "aria-expanded" attributes on the trigger button + +#### Returns + +Type: `Promise` + + + + +---------------------------------------------- + +*Built with [StencilJS](https://stenciljs.com/)* diff --git a/packages/components/src/components/post-collapsible/post-collapsible.scss b/packages/components/src/components/post-collapsible/post-collapsible.scss index 0aaf688255..20947d5cd1 100644 --- a/packages/components/src/components/post-collapsible/post-collapsible.scss +++ b/packages/components/src/components/post-collapsible/post-collapsible.scss @@ -1,7 +1,4 @@ :host { display: block; -} - -.collapse { overflow: hidden; } diff --git a/packages/components/src/components/post-collapsible/post-collapsible.tsx b/packages/components/src/components/post-collapsible/post-collapsible.tsx index 41a29b4323..7473f56975 100644 --- a/packages/components/src/components/post-collapsible/post-collapsible.tsx +++ b/packages/components/src/components/post-collapsible/post-collapsible.tsx @@ -7,7 +7,6 @@ import { Host, Method, Prop, - State, Watch, } from '@stencil/core'; import { version } from '@root/package.json'; @@ -26,12 +25,9 @@ import { checkEmptyOrType, isMotionReduced } from '@/utils'; export class PostCollapsible { private isLoaded = false; private isOpen = true; - private collapsible: HTMLElement; @Element() host: HTMLPostCollapsibleElement; - @State() id: string; - /** * If `true`, the element is initially collapsed otherwise it is displayed. */ @@ -57,13 +53,11 @@ export class PostCollapsible { this.validateCollapsed(); } - componentWillRender() { - this.id = this.host.id || `c${crypto.randomUUID()}`; - } - componentDidLoad() { if (this.collapsed) void this.toggle(false); this.isLoaded = true; + + this.updateTriggers(); } /** @@ -78,7 +72,7 @@ export class PostCollapsible { this.isOpen = !this.isOpen; if (this.isLoaded) this.postToggle.emit(this.isOpen); - const animation = open ? expand(this.collapsible) : collapse(this.collapsible); + const animation = open ? expand(this.host) : collapse(this.host); if (!this.isLoaded || isMotionReduced()) animation.finish(); @@ -89,12 +83,21 @@ export class PostCollapsible { return this.isOpen; } + /** + * Update all post-collapsible-trigger elements referring to the collapsible + */ + private updateTriggers() { + const triggers: NodeListOf = document.querySelectorAll( + `post-collapsible-trigger[for=${this.host.id}]`, + ); + + triggers.forEach(trigger => trigger.update()); + } + render() { return ( - -
(this.collapsible = el)}> - -
+ + ); } diff --git a/packages/components/src/index.ts b/packages/components/src/index.ts index 6ad5b611b6..0130213e14 100644 --- a/packages/components/src/index.ts +++ b/packages/components/src/index.ts @@ -6,6 +6,7 @@ export { PostAccordionItem } from './components/post-accordion-item/post-accordi export { PostAlert } from './components/post-alert/post-alert'; export { PostCardControl } from './components/post-card-control/post-card-control'; export { PostCollapsible } from './components/post-collapsible/post-collapsible'; +export { PostCollapsibleTrigger } from './components/post-collapsible-trigger/post-collapsible-trigger'; export { PostIcon } from './components/post-icon/post-icon'; export { PostPopover } from './components/post-popover/post-popover'; export { PostPopovercontainer } from './components/post-popovercontainer/post-popovercontainer'; diff --git a/packages/components/src/utils/debounce.ts b/packages/components/src/utils/debounce.ts new file mode 100644 index 0000000000..fe68e14988 --- /dev/null +++ b/packages/components/src/utils/debounce.ts @@ -0,0 +1,7 @@ +export function debounce(callback: (...args: T) => void, timeout = 200) { + let id: ReturnType; + return (...args: T) => { + if (id) clearTimeout(id); + id = setTimeout(callback, timeout, ...args); + }; +} diff --git a/packages/components/src/utils/index.ts b/packages/components/src/utils/index.ts index 6756aaf369..b9ed27b33f 100644 --- a/packages/components/src/utils/index.ts +++ b/packages/components/src/utils/index.ts @@ -1,3 +1,4 @@ export * from './property-checkers'; +export * from './debounce'; export * from './is-motion-reduced'; export * from './sass-export'; diff --git a/packages/components/src/utils/tests/debounce.spec.ts b/packages/components/src/utils/tests/debounce.spec.ts new file mode 100644 index 0000000000..d48eb5b757 --- /dev/null +++ b/packages/components/src/utils/tests/debounce.spec.ts @@ -0,0 +1,48 @@ +import { debounce } from '../debounce'; + +const timeout = 50; + +describe('debounce', () => { + let callback: jest.MockedFn<(...args: unknown[]) => void>; + let debouncedCallback: (...args: unknown[]) => void; + + beforeEach(() => { + callback = jest.fn(); + debouncedCallback = debounce(callback, timeout); + }); + + it('should wait until the provided timeout elapses before executing the callback function', done => { + debouncedCallback(); + + setTimeout(() => { + expect(callback).not.toHaveBeenCalled(); + }, timeout / 2); + + setTimeout(() => { + expect(callback).toHaveBeenCalled(); + done(); + }, timeout * 2); + }); + + it('should only execute the callback function once when the timeout elapses', done => { + debouncedCallback(); + debouncedCallback(); + debouncedCallback(); + + setTimeout(() => { + expect(callback).toHaveBeenCalledTimes(1); + done(); + }, timeout * 2); + }); + + it('should pass all provided arguments to the callback function', done => { + const args = [25, 'myArg', false]; + + debouncedCallback(...args); + + setTimeout(() => { + expect(callback).toHaveBeenCalledWith(...args); + done(); + }, timeout * 2); + }); +}); diff --git a/packages/documentation/src/stories/components/collapsible/collapsible.docs.mdx b/packages/documentation/src/stories/components/collapsible/collapsible.docs.mdx index ab60064f56..60f4145bda 100644 --- a/packages/documentation/src/stories/components/collapsible/collapsible.docs.mdx +++ b/packages/documentation/src/stories/components/collapsible/collapsible.docs.mdx @@ -24,19 +24,13 @@ To make the collapsible content hidden by default, just use the `collapsible="tr -### Custom Trigger +### Programmatic Toggle -The `` component offers a `.toggle()` method that allows to trigger the collapse programmatically. +The `` component offers a `.toggle()` method that allows you to trigger the collapse programmatically. This method is asynchronous and returns a promise that resolves with the current open state. -It optionally takes a boolean parameter that forces open when `true` or close when `false`. +It optionally takes a boolean parameter: `true` forces it open, and `false` forces it closed. - -To ensure good accessibility, identify the collapsible with an `id`, -then add an `aria-controls` attribute to your control element referencing this `id`. -Also make sure to add an `aria-expanded` attribute to the control element: -if the collapsible element is closed, the attribute on the control element must have a value of `aria-expanded="false"` -and `aria-expanded="true"` otherwise. diff --git a/packages/documentation/src/stories/components/collapsible/collapsible.snapshot.stories.ts b/packages/documentation/src/stories/components/collapsible/collapsible.snapshot.stories.ts index 38e0585172..552139bb72 100644 --- a/packages/documentation/src/stories/components/collapsible/collapsible.snapshot.stories.ts +++ b/packages/documentation/src/stories/components/collapsible/collapsible.snapshot.stories.ts @@ -2,13 +2,12 @@ import { html } from 'lit'; import type { Args, StoryContext, StoryObj } from '@storybook/web-components'; import { bombArgs } from '@/utils'; -import meta, { Default } from './collapsible.stories'; +import meta from './collapsible.stories'; const { id, ...metaWithoutId } = meta; export default { ...metaWithoutId, - decorators: [], title: 'Snapshots', }; @@ -16,21 +15,26 @@ type Story = StoryObj; export const Collapsible: Story = { render: (_args: Args, context: StoryContext) => { - const templateVariants = bombArgs({ - collapsed: [false, true], - }).map((args: Args) => { - return html` -
-

collapsed: ${args.collapsed}

- ${meta.render?.({ ...context.args, ...Default.args, ...args }, context)} -
- `; - }); - return html`
${['white', 'dark'].map( - bg => html`
${templateVariants}
`, + bg => html` +
+ ${bombArgs({ + collapsed: [false, true], + }).map( + (args: Args, i: number) => html` +
+

collapsed: ${args.collapsed}

+ ${meta.render?.( + { ...context.args, ...args }, + { ...context, id: `${context.id}-${bg}-${i}` }, + )} +
+ `, + )} +
+ `, )}
`; diff --git a/packages/documentation/src/stories/components/collapsible/collapsible.stories.ts b/packages/documentation/src/stories/components/collapsible/collapsible.stories.ts index ad58063b28..ea034d8e98 100644 --- a/packages/documentation/src/stories/components/collapsible/collapsible.stories.ts +++ b/packages/documentation/src/stories/components/collapsible/collapsible.stories.ts @@ -2,6 +2,7 @@ import { StoryContext, StoryFn, StoryObj } from '@storybook/web-components'; import { html } from 'lit'; import { spreadArgs } from '@/utils'; import { MetaComponent } from '@root/types'; +import { unsafeHTML } from 'lit/directives/unsafe-html.js'; const meta: MetaComponent = { id: '6a91848c-16ec-4a23-bc45-51c797b5b2c3', @@ -9,7 +10,7 @@ const meta: MetaComponent = { tags: ['package:WebComponents'], component: 'post-collapsible', render: renderCollapsible, - decorators: [externalControls], + decorators: [gap], parameters: { badges: [], controls: { @@ -25,46 +26,26 @@ const meta: MetaComponent = { export default meta; // DECORATORS -function externalControls(story: StoryFn, context: StoryContext) { - const { args, canvasElement } = context; - const togglerId = `button--${context.id}`; - - let collapsible!: HTMLPostCollapsibleElement; - let toggler!: HTMLButtonElement; - setTimeout(async () => { - collapsible = canvasElement.querySelector('post-collapsible') as HTMLPostCollapsibleElement; - toggler = canvasElement.querySelector(`#${togglerId}`) as HTMLButtonElement; - - await collapsible.componentOnReady(); - - toggler.setAttribute('aria-controls', collapsible.id); - }); - - const toggle = async () => { - const isOpen = await collapsible.toggle(); - toggler.setAttribute('aria-expanded', String(isOpen)); - }; +function gap(story: StoryFn, context: StoryContext) { + return html`
${story(context.args, context)}
`; +} +//RENDERER +function renderCollapsible( + { innerHTML, ...args }: Partial, + context: StoryContext, +) { return html` - + + + - ${story(args, context)} + + ${unsafeHTML(innerHTML)} + `; } -//RENDERER -function renderCollapsible(args: Partial) { - return html` `; -} - // STORIES type Story = StoryObj;