diff --git a/.changeset/true-guests-drive.md b/.changeset/true-guests-drive.md new file mode 100644 index 0000000000..1452649f83 --- /dev/null +++ b/.changeset/true-guests-drive.md @@ -0,0 +1,5 @@ +--- +"@stackoverflow/stacks-svelte": minor +--- + +Added aria-label and aria-labelledby props to PopoverContent diff --git a/packages/stacks-svelte/src/components/Popover/Popover.test.ts b/packages/stacks-svelte/src/components/Popover/Popover.test.ts index b9fa312335..9f54642965 100644 --- a/packages/stacks-svelte/src/components/Popover/Popover.test.ts +++ b/packages/stacks-svelte/src/components/Popover/Popover.test.ts @@ -193,6 +193,75 @@ describe("Popover", () => { expect(innerContentElement).to.have.class("custom-class"); }); + it("should add aria-label to the popover when the ariaLabel prop is provided", async () => { + render(Popover, { + props: { + ...defaultProps, + autoshow: true, + children: createSvelteComponentsSnippet([ + defaultChildren.reference, + { + component: PopoverContent, + props: { + ariaLabel: "Popover with content", + children: createRawSnippet(() => ({ + render: () => "Popover Content", + })), + }, + }, + ]), + }, + }); + + expect(screen.getByRole("dialog")).to.have.attribute( + "aria-label", + "Popover with content" + ); + }); + + it("should add aria-labelledby to the popover when the ariaLabelledby prop is provided", async () => { + render(Popover, { + props: { + ...defaultProps, + autoshow: true, + children: createSvelteComponentsSnippet([ + defaultChildren.reference, + { + component: PopoverContent, + props: { + ariaLabelledby: "my-label-id", + children: createRawSnippet(() => ({ + render: () => "Popover Content", + })), + }, + }, + ]), + }, + }); + + expect(screen.getByRole("dialog")).to.have.attribute( + "aria-labelledby", + "my-label-id" + ); + }); + + it("should not add aria-label or aria-labelledby when not provided", async () => { + render(Popover, { + props: { + ...defaultProps, + autoshow: true, + children: createSvelteComponentsSnippet([ + defaultChildren.reference, + defaultChildren.content, + ]), + }, + }); + + const dialog = screen.getByRole("dialog"); + expect(dialog).not.to.have.attribute("aria-label"); + expect(dialog).not.to.have.attribute("aria-labelledby"); + }); + it("add classes to the popover close button component when the class prop is provided", async () => { render(Popover, { props: { diff --git a/packages/stacks-svelte/src/components/Popover/PopoverContent.svelte b/packages/stacks-svelte/src/components/Popover/PopoverContent.svelte index e50ece48ee..2d1d6979f2 100644 --- a/packages/stacks-svelte/src/components/Popover/PopoverContent.svelte +++ b/packages/stacks-svelte/src/components/Popover/PopoverContent.svelte @@ -9,6 +9,14 @@ * (if not specified, it will default to 'dialog' for popovers or 'tooltip' when in tooltip mode) */ role?: string | null; + /** + * Accessible label for the popover + */ + ariaLabel?: string; + /** + * ID of an element that labels the popover + */ + ariaLabelledby?: string; /** * Additional CSS classes added to the s-popover element */ @@ -25,6 +33,8 @@ let { role = null, + ariaLabel, + ariaLabelledby, class: className = "", contentClass = "", children, @@ -65,6 +75,8 @@ id={`${pstate.id}-popover`} class={computedClass} role={computedRole} + aria-label={ariaLabel} + aria-labelledby={ariaLabelledby} use:pstate.floatingContent use:focusTrap={{ active: pstate.trapFocus && !!pstate.visible }} use:clickOutside