Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions MIGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -627,6 +627,16 @@ The PopoverProvider component acts as a counterpoint to WithTooltip. When you wa

PopoverProvider is based on react-aria. It must have a single child that acts as a trigger. This child must have a pressable role (can be clicked or pressed) and must be able to receive React refs. Wrap your trigger component in `forwardRef` if you notice placement issues for your popover.

##### Added: ariaLabel

The `ariaLabel` prop was added in Storybook 10.3 to provide an accessible label for the popover dialog. This label is announced by screen readers when the popover opens. `ariaLabel` will become mandatory in Storybook 11.

```tsx
<PopoverProvider ariaLabel="Share options" popover={<ShareMenu />}>
<Button ariaLabel="Share">Share</Button>
</PopoverProvider>
```

#### WithTooltip Component API Changes

The WithTooltip component has been reimplemented from the ground up, under the new name `TooltipProvider`. The new implementation will replace `WithTooltip` entirely in Storybook 11. Below is a summary of the changes between both APIs, which will take full effect in Storybook 11.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ const ArgSummary: FC<ArgSummaryProps> = ({ value, initialExpandedArgs }) => {

return (
<PopoverProvider
ariaLabel="Arg value details"
placement="bottom"
visible={isOpen}
onVisibleChange={(isVisible) => {
Expand Down
1 change: 1 addition & 0 deletions code/addons/docs/src/blocks/controls/Color.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,7 @@ export const ColorControl: FC<ColorControlProps> = ({
placeholder="Choose color..."
/>
<PopoverProvider
ariaLabel="Color picker"
defaultVisible={startOpen}
visible={readOnly ? false : undefined}
onVisibleChange={() => color && addPreset(color)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ const meta = preview.meta({
title: 'Overlay/PopoverProvider',
component: PopoverProvider,
args: {
ariaLabel: 'Sample popover',
hasChrome: true,
offset: 8,
placement: 'top',
Expand Down
35 changes: 32 additions & 3 deletions code/core/src/components/components/Popover/PopoverProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import type { DOMAttributes, ReactElement, ReactNode } from 'react';
import React, { useCallback, useState } from 'react';
import React, { cloneElement, useCallback, useState } from 'react';

import { deprecate } from 'storybook/internal/client-logger';

import { Pressable } from '@react-aria/interactions';
import { DialogTrigger } from 'react-aria-components/patched-dist/Dialog';
Expand All @@ -9,6 +11,12 @@ import { type PopperPlacement, convertToReactAriaPlacement } from '../shared/ove
import { Popover } from './Popover';

export interface PopoverProviderProps {
/**
* An accessible label for the popover dialog, announced by screen readers. This prop will become
* mandatory in Storybook 11. Provide a concise description of the popover's purpose.
*/
ariaLabel?: string;

/** Whether to display the Popover in a prestyled container. True by default. */
hasChrome?: boolean;

Expand Down Expand Up @@ -53,6 +61,7 @@ export interface PopoverProviderProps {
}

export const PopoverProvider = ({
ariaLabel,
placement: placementProp = 'bottom-start',
hasChrome = true,
hasCloseButton = false,
Expand All @@ -66,6 +75,12 @@ export const PopoverProvider = ({
onVisibleChange,
...props
}: PopoverProviderProps) => {
if (!ariaLabel) {
deprecate(
"The 'ariaLabel' prop on 'PopoverProvider' will become mandatory in Storybook 11. Provide a concise, accessible label describing the popover's purpose."
);
}

// Map Popper.js placement to react-aria placement best we can.
const placement = convertToReactAriaPlacement(placementProp);

Expand All @@ -86,8 +101,22 @@ export const PopoverProvider = ({
onOpenChange={onOpenChange}
{...props}
>
<Pressable>{children}</Pressable>
<PopoverUpstream placement={placement} offset={offset} style={{ outline: 'none' }}>
<Pressable>
{
/* React-aria does not inject aria-haspopup='dialog' to support legacy screen readers, so we do it ourselves. */
cloneElement(
children,
// @ts-expect-error aria-haspopup is a valid ARIA attribute but cloneElement types are too strict
{ 'aria-haspopup': 'dialog' }
)
}
</Pressable>
<PopoverUpstream
aria-label={ariaLabel}
placement={placement}
offset={offset}
style={{ outline: 'none' }}
>
<Popover
hasChrome={hasChrome}
hideLabel={closeLabel}
Expand Down
1 change: 1 addition & 0 deletions code/core/src/components/components/Tabs/Tabs.hooks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ export function useList(list: ChildrenListComplete) {
return (
<>
<PopoverProvider
ariaLabel="Additional tabs"
visible={isTooltipVisible}
onVisibleChange={setTooltipVisible}
placement="bottom"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,13 @@ const WithPopoverDecorator: DecoratorFunction = (storyFn) => (
height: '300px',
}}
>
<PopoverProvider placement="top" visible padding={0} popover={storyFn()}>
<PopoverProvider
ariaLabel="Tooltip message"
placement="top"
visible
padding={0}
popover={storyFn()}
>
<Button ariaLabel={false}>Popover</Button>
</PopoverProvider>
</div>
Expand Down
1 change: 1 addition & 0 deletions code/core/src/manager/components/preview/tools/share.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ export const shareTool: Addon_BaseType = {
{({ api, storyId, refId }) =>
storyId ? (
<PopoverProvider
ariaLabel="Share this story"
hasChrome
placement="bottom"
padding={0}
Expand Down
3 changes: 2 additions & 1 deletion code/core/src/manager/components/sidebar/ChecklistWidget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ export const ChecklistWidget = () => {
<CollapseToggle
{...toggleProps}
id="checklist-module-collapse-toggle"
ariaLabel={`${isCollapsed ? 'Expand' : 'Collapse'} onboarding checklist`}
ariaLabel={`${isCollapsed ? 'Expand' : 'Collapse'} onboarding guide`}
>
<ChevronSmallUpIcon
style={{
Expand All @@ -259,6 +259,7 @@ export const ChecklistWidget = () => {
</CollapseToggle>
{loaded && (
<PopoverProvider
ariaLabel="Onboarding guide menu"
padding={0}
popover={({ onHide }) => (
<ActionList>
Expand Down
1 change: 1 addition & 0 deletions code/core/src/manager/components/sidebar/ContextMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ export const useContextMenu = (context: API_HashEntry, links: Link[], api: API)
onMouseEnter: handlers.onMouseEnter,
node: shouldRender ? (
<PopoverProvider
ariaLabel="Context menu"
placement="bottom-end"
defaultVisible={false}
visible={isOpen}
Expand Down
1 change: 1 addition & 0 deletions code/core/src/manager/components/sidebar/Menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@ export const SidebarMenu: FC<SidebarMenuProps> = ({ menu, isHighlighted, onClick

return (
<PopoverProvider
ariaLabel="Storybook menu"
placement={'bottom-start'}
padding={0}
popover={({ onHide }) => <SidebarMenuList onHide={onHide} menu={menu} />}
Expand Down
1 change: 1 addition & 0 deletions code/core/src/manager/components/sidebar/RefBlocks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ export const ErrorBlock: FC<{ error: Error }> = ({ error }) => {
Oh no! Something went wrong loading this Storybook.
<br />
<PopoverProvider
ariaLabel="Error details"
hasCloseButton
offset={isMobile ? 0 : 8}
placement={isMobile ? 'bottom-end' : 'bottom-start'}
Expand Down
1 change: 1 addition & 0 deletions code/core/src/manager/components/sidebar/RefIndicator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ export const RefIndicator = React.memo(
return (
<IndicatorPlacement ref={forwardedRef}>
<PopoverProvider
ariaLabel="Composed Storybook status"
placement={isMobile ? 'bottom' : 'bottom-start'}
padding={0}
popover={() => (
Expand Down
2 changes: 1 addition & 1 deletion code/core/src/manager/components/sidebar/TagsFilter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@ export const TagsFilter = ({ api, indexJson, tagPresets }: TagsFilterProps) => {

return (
<PopoverProvider
ariaLabel="Tag filters"
placement="bottom"
onVisibleChange={setExpanded}
offset={8}
Expand All @@ -236,7 +237,6 @@ export const TagsFilter = ({ api, indexJson, tagPresets }: TagsFilterProps) => {
key="tags"
ariaLabel="Tag filters"
ariaDescription="Filter the items shown in a sidebar based on the tags applied to them."
aria-haspopup="dialog"
variant="ghost"
padding="small"
isHighlighted={tagsActive}
Expand Down
8 changes: 7 additions & 1 deletion code/core/src/manager/container/Menu.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,13 @@ export default {
height: '300px',
}}
>
<PopoverProvider placement="top" defaultVisible padding={0} popover={storyFn()}>
<PopoverProvider
ariaLabel="Menu"
placement="top"
defaultVisible
padding={0}
popover={storyFn()}
>
<Button ariaLabel={false}>Click me</Button>
</PopoverProvider>
</div>
Expand Down
Loading