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
76 changes: 76 additions & 0 deletions MIGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,23 @@
- [Removed: onEscapeKeyDown](#removed-onescapekeydown)
- [Added: `ariaLabel`](#added-arialabel-1)
- [Renamed: Modal.Dialog.Close and Modal.CloseButton](#renamed-modaldialogclose-and-modalclosebutton)
- [ListItem, TooltipLinkList and TooltipMessage are deprecated](#listitem-tooltiplinklist-and-tooltipmessage-are-deprecated)
- [Tooltip Component API Changes](#tooltip-component-api-changes)
- [Renamed: tooltipRef](#renamed-tooltipref)
- [Removed: arrowProps and withArrows](#removed-arrowprops-and-witharrows)
- [Removed: placement](#removed-placement)
- [Changed type: color](#changed-type-color)
- [WithPopover Component Added](#withpopover-component-added)
- [WithTooltip Component API Changes](#withtooltip-component-api-changes)
- [Removed: trigger](#removed-trigger)
- [Added: triggerOnFocusOnly](#added-triggeronfocusonly)
- [Renamed: startOpen](#renamed-startopen)
- [Removed: svg, strategy, withArrows, mutationObserverOptions](#removed-svg-strategy-witharrows-mutationobserveroptions)
- [Removed: hasChrome](#removed-haschrome)
- [Removed: closeOnTriggerHidden, followCursor, closeOnOutsideClick](#removed-closeontriggerhidden-followcursor-closeonoutsideclick)
- [Removed: interactive](#removed-interactive)
- [Other changes](#other-changes)
- [WithTooltipPure and WithTooltipState are removed](#withtooltippure-and-withtooltipstate-are-removed)
- [From version 8.x to 9.0.0](#from-version-8x-to-900)
- [Core Changes and Removals](#core-changes-and-removals)
- [Dropped support for legacy packages](#dropped-support-for-legacy-packages)
Expand Down Expand Up @@ -649,6 +666,65 @@ Modal elements must have a title to be accessible. Set that title through the ma
The `Modal.Dialog.Close` component and `Modal.CloseButton` components are replaced by `Modal.Close` for consistency with other components. You may call `<Modal.Close />` for a default close button, or `<Modal.Close asChild>...</Modal.Close>` to wrap your own custom button.

The `Modal.Close` component no longer requires an `onClick` handler to close the modal. It will automatically close the modal when clicked. If you need to perform additional actions when the close button is clicked, you can still provide an `onClick` handler, and it will be called in addition to closing the modal.
#### ListItem, TooltipLinkList and TooltipMessage are deprecated

The ListItem and TooltipLinkList components were used in Storybook to make menus, and TooltipMessage to make message popovers. However, WithTooltip does not support keyboard interactions, so these components were not accessible.

These components are now deprecated and will be removed in future versions. To replace TooltipMessage, replace WithTooltip with WithPopover, and use Popover as a base component for your popovers. To replace ListItem and TooltipLinkList, a dedicated menu component will be introduced in a future version, and Popover can be used in the meantime.

#### Tooltip Component API Changes

##### Renamed: tooltipRef
Tooltip's `ref` prop is now named `ref` for consistency.

##### Removed: arrowProps and withArrows
The `arrowProps` and `withArrows` props were not used in Storybook, so they have been removed.

We recommend you do not use arrows in your addon tooltips for better consistency with the Storybook UI.

##### Removed: placement
The `placement` prop was passed to help position the arrow. It has also been removed. WithToolip now entirely handles the placement of its tooltip on its own.

##### Changed type: color
The `color` prop used to accept arbitrary colors and theme background color names. This made it difficult to use.

The prop was to the background color of the tooltip, and it was not possible to set the text color in a consistent fashion. To ensure Tooltip uses accessible colors, the prop has been limited to the following values: `'default'`, `'inverse'`, `'positive'`, `'negative'`, `'warning'` and `'none'`. The prop now controls both background and foreground colors.

#### WithPopover Component Added

The WithPopover component acts as a counterpoint to WithTooltip. When you want an interactive overlay with buttons or inputs, use WithPopover and Popover. When you want a static overlay that shows on focus or hover, use WithTooltip with TooltipNote or Tooltip.

WithPopover 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.

#### WithTooltip Component API Changes

##### Removed: trigger
The `trigger` prop was removed to enforce better accessibility compliance. WithTooltip must not be triggered on click, as it is not reachable by keyboard. Buttons that open a popover, menu or select must use appropriate components instead.

#### Added: triggerOnFocusOnly
The `triggerOnFocusOnly` prop was added. When set, tooltips will only show on focus. Use this to provide keyboard navigation hints to keyboard users. Do not use it for other purposes.

#### Renamed: startOpen
The `startOpen` prop was renamed `defaultVisible` to match naming in other components that expose both controlled and uncontrolled visibility.

#### Removed: svg, strategy, withArrows, mutationObserverOptions
These prop were not used inside Storybook and have been removed.

#### Removed: hasChrome
The `hasChrome` prop was removed because it should be handled by the tooltip being shown instead. Popover and Tooltip both have a `hasChrome` prop. TooltipNote never needs this prop and does not have it.

#### Removed: closeOnTriggerHidden, followCursor, closeOnOutsideClick
The `closeOnTriggerHidden`, `followCursor` and `closeOnOutsideClick` prop has been removed. WithTooltip will now authoritatively decide when and where to show or hide its tooltip. It will always close on clicks outside the tooltip, because tooltips should never be modal.

#### Removed: interactive
Thed `interactive` prop has been removed as it does not align with our vision for accessible components with a well-defined role. Use WithPopover instead of WithTooltip to show interactive overlays.

##### Other changes
The underlying implementation was switched from Popper.js to react-aria. Due to these changes, WithTooltip must now have a single child that has a focusable role and that can receive React refs. Wrap your trigger component in `forwardRef` if you notice placement issues for your tooltip.

#### WithTooltipPure and WithTooltipState are removed

Instead, use WithTooltip. For a controlled tooltip, use the `onVisibleChange` and `visible` props. For an uncontrolled tooltip with a default open state, use the `defaultVisible` prop.

## From version 8.x to 9.0.0

Expand Down
7 changes: 3 additions & 4 deletions code/addons/docs/src/blocks/components/ArgsTable/ArgValue.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { FC } from 'react';
import React, { useState } from 'react';

import { SyntaxHighlighter, WithTooltipPure, codeCommon } from 'storybook/internal/components';
import { SyntaxHighlighter, WithTooltip, codeCommon } from 'storybook/internal/components';

import { ChevronSmallDownIcon, ChevronSmallUpIcon } from '@storybook/icons';

Expand Down Expand Up @@ -171,8 +171,7 @@ const ArgSummary: FC<ArgSummaryProps> = ({ value, initialExpandedArgs }) => {
}

return (
<WithTooltipPure
closeOnOutsideClick
<WithTooltip
placement="bottom"
visible={isOpen}
onVisibleChange={(isVisible) => {
Expand All @@ -190,7 +189,7 @@ const ArgSummary: FC<ArgSummaryProps> = ({ value, initialExpandedArgs }) => {
<span>{summaryAsString}</span>
{isOpen ? <ChevronUpIcon /> : <ChevronDownIcon />}
</Expandable>
</WithTooltipPure>
</WithTooltip>
);
};

Expand Down
1 change: 0 additions & 1 deletion code/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,6 @@
"react-dom": "^18.2.0",
"react-helmet-async": "^1.3.0",
"react-inspector": "^6.0.0",
"react-popper-tooltip": "^4.4.2",
"react-router-dom": "6.15.0",
"react-stately": "^3.41.0",
"react-syntax-highlighter": "^15.4.5",
Expand Down
38 changes: 20 additions & 18 deletions code/core/src/components/components/Button/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -109,24 +109,26 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
const finalTooltip = tooltip || (ariaLabel !== false ? ariaLabel : undefined);

return (
<InteractiveTooltipWrapper shortcut={shortcut} tooltip={finalTooltip}>
<StyledButton
as={Comp}
ref={ref}
variant={variant}
size={size}
padding={padding}
disabled={disabled}
animating={isAnimating}
animation={animation}
onClick={handleClick}
aria-label={ariaLabel !== false ? ariaLabel : undefined}
aria-keyshortcuts={shortcutAttribute}
{...ariaDescriptionAttrs}
{...props}
/>
<>
<InteractiveTooltipWrapper shortcut={shortcut} tooltip={finalTooltip}>
<StyledButton
as={Comp}
ref={ref}
variant={variant}
size={size}
padding={padding}
disabled={disabled}
animating={isAnimating}
animation={animation}
onClick={handleClick}
aria-label={ariaLabel !== false ? ariaLabel : undefined}
aria-keyshortcuts={shortcutAttribute}
{...ariaDescriptionAttrs}
{...props}
/>
</InteractiveTooltipWrapper>
<AriaDescription />
</InteractiveTooltipWrapper>
</>
);
}
);
Expand Down Expand Up @@ -291,7 +293,7 @@ const StyledButton = styled('button', {

export const IconButton = forwardRef<HTMLButtonElement, ButtonProps>((props, ref) => {
deprecate(
'`IconButton` is deprecated and will be removed in Storybook 10, use `Button` instead.'
'`IconButton` is deprecated and will be removed in Storybook 11, use `Button` instead.'
);

return <Button ref={ref} {...props} />;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,11 @@
import React, { useMemo } from 'react';
import React, { type DOMAttributes, type ReactElement, useMemo } from 'react';

import { TooltipNote, WithTooltip } from 'storybook/internal/components';
import { shortcutToHumanString } from 'storybook/internal/manager-api';
import type { API_KeyCollection } from 'storybook/internal/manager-api';

import { styled } from 'storybook/theming';

import { TooltipNote } from '../../tooltip/TooltipNote';
import { WithTooltip } from '../../tooltip/WithTooltip';

const NoMarginNote = styled(TooltipNote)(() => ({
margin: 0,
}));

// TODO: Improve delay management; make the delay near-instantaneous if any instance of this component has been recently shown.
// TODO: Find way to trigger the tooltip when the child component is focused too. Will need this for general a11y but particularly for Sidebar nav secondary actions.
export const InteractiveTooltipWrapper: React.FC<{
children: React.ReactNode;
children: ReactElement<DOMAttributes<Element>, string>;
shortcut?: API_KeyCollection;
tooltip?: string;
}> = ({ children, shortcut, tooltip }) => {
Expand All @@ -37,7 +27,7 @@ export const InteractiveTooltipWrapper: React.FC<{
}, [shortcut, tooltip]);

return tooltipLabel ? (
<WithTooltip trigger="hover" hasChrome={false} tooltip={<NoMarginNote note={tooltipLabel} />}>
<WithTooltip placement="top" tooltip={<TooltipNote note={tooltipLabel} />}>
{children}
</WithTooltip>
) : (
Expand Down
5 changes: 2 additions & 3 deletions code/core/src/components/components/tabs/tabs.hooks.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import React, { useCallback, useLayoutEffect, useRef, useState } from 'react';

import type { TooltipLinkListLink as Link } from 'storybook/internal/components';
import { TooltipLinkList, WithTooltip } from 'storybook/internal/components';
import { sanitize } from 'storybook/internal/csf';

import { styled } from 'storybook/theming';
import useResizeObserver from 'use-resize-observer';

import { TabButton } from '../bar/button';
import { TooltipLinkList } from '../tooltip/TooltipLinkList';
import type { Link } from '../tooltip/TooltipLinkList';
import { WithTooltip } from '../tooltip/WithTooltip';
import type { ChildrenListComplete } from './tabs.helpers';

const CollapseIcon = styled.span<{ isActive: boolean }>(({ theme, isActive }) => ({
Expand Down
6 changes: 6 additions & 0 deletions code/core/src/components/components/tooltip/ListItem.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import React, { type ComponentProps, type ReactNode, type SyntheticEvent } from 'react';

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

import memoize from 'memoizerific';
import { styled } from 'storybook/theming';

Expand Down Expand Up @@ -210,6 +212,10 @@ const ListItem = (props: ListItemProps) => {
const itemProps = getItemProps(props);
const left = icon || input;

deprecate(
'`ListItem` is deprecated and will be removed in Storybook 11, use `MenuItem` instead.'
);

return (
<Item {...rest} {...commonProps} {...itemProps}>
<>
Expand Down
127 changes: 63 additions & 64 deletions code/core/src/components/components/tooltip/Tooltip.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,84 +1,83 @@
import type { ComponentProps } from 'react';
import React from 'react';

import { styled } from 'storybook/theming';

import preview from '../../../../../.storybook/preview';
import { Tooltip } from './Tooltip';

// Popper would position the tooltip absolutely. We just need to make sure we are pos:rel
const mockPopperProps = {
style: {
position: 'relative',
top: 20,
left: 20,
const SampleTooltip = () => (
<div>
<h3>Lorem ipsum dolor sit amet</h3>
<p>Consectatur vestibulum concet durum politu coret weirom</p>
</div>
);

const meta = preview.meta({
id: 'overlay-Tooltip',
title: 'Overlay/Tooltip',
component: Tooltip,
args: {
children: <SampleTooltip />,
color: undefined,
hasChrome: true,
},
argTypes: {
color: {
type: 'string',
control: 'select',
options: ['default', 'inverse', 'positive', 'negative', 'warning', 'none'],
},
},
};
const Content = styled.div({
width: '100px',
height: '100px',
fontSize: '16px',
textAlign: 'center',
lineHeight: '100px',
});

export default {
component: Tooltip,
args: mockPopperProps,
};
export const Base = meta.story({
args: {
children: <SampleTooltip />,
},
});

export const WithChrome = meta.story({
args: {
hasChrome: true,
},
});

export const WithoutChrome = meta.story({
args: {
hasChrome: false,
},
});

export const BasicDefault = {
// args: mockPopperProps,
render: (args: ComponentProps<typeof Tooltip>) => (
<Tooltip {...args}>
<Content>Text</Content>
</Tooltip>
),
};
export const ColorDefault = meta.story({
args: {
color: 'default',
},
});

export const BasicDefaultBottom = {
export const ColorInverse = meta.story({
args: {
// ...mockPopperProps,
placement: 'bottom',
color: 'inverse',
},
render: (args: ComponentProps<typeof Tooltip>) => (
<Tooltip {...args}>
<Content>Text</Content>
</Tooltip>
),
};
});

export const BasicDefaultLeft = {
export const ColorPositive = meta.story({
args: {
// ...mockPopperProps,
placement: 'left',
color: 'positive',
},
render: (args: ComponentProps<typeof Tooltip>) => (
<Tooltip {...args}>
<Content>Text</Content>
</Tooltip>
),
};
});

export const BasicDefaultRight = {
export const ColorNegative = meta.story({
args: {
// ...mockPopperProps,
placement: 'right',
color: 'negative',
},
render: (args: ComponentProps<typeof Tooltip>) => (
<Tooltip {...args}>
<Content>Text</Content>
</Tooltip>
),
};
});

export const WithoutChrome = {
export const ColorWarning = meta.story({
args: {
// ...mockPopperProps,
hasChrome: false,
color: 'warning',
},
render: (args: ComponentProps<typeof Tooltip>) => (
<Tooltip {...args}>
<Content>Text</Content>
</Tooltip>
),
};
});

export const WithoutColor = meta.story({
args: {
color: 'none',
},
});
Loading
Loading