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
25 changes: 25 additions & 0 deletions MIGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@
- [Added: tooltip](#added-tooltip)
- [Removed: active](#removed-active)
- [IconButton is deprecated](#iconbutton-is-deprecated)
- [Modal Component API Changes](#modal-component-api-changes)
- [Removed: container and portalSelector](#removed-container-and-portalselector)
- [Removed: onInteractOutside](#removed-oninteractoutside)
- [Removed: onEscapeKeyDown](#removed-onescapekeydown)
- [Added: `ariaLabel`](#added-arialabel-1)
- [Renamed: Modal.Dialog.Close and Modal.CloseButton](#renamed-modaldialogclose-and-modalclosebutton)
- [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 @@ -625,6 +631,25 @@ The IconButton component is deprecated, as it overlaps with Button. Instead, use

IconButton will be removed in future versions.

#### Modal Component API Changes

##### Removed: container and portalSelector
The `container` and `portalSelector` props were not used inside Storybook, so they have been removed. The new Modal component does not support custom portal locations, because it is not recommended practice. A single portal at the end of the document ensures modals appear in their order of creation and are never cropped by CSS `overflow` properties.

##### Removed: onInteractOutside
The `onInteractOutside` prop is removed in favor of `dismissOnClickOutside`, because it was only used to close the modal when clicking outside. Use `dismissOnClickOutside` to control whether clicking outside the modal should close it or not.

##### Removed: onEscapeKeyDown
The `onEscapeKeyDown` prop is removed in favor of `dismissOnEscape`, because it was only used to close the modal when pressing Escape. Use `dismissOnEscape` to control whether pressing Escape should close it or not.

##### Added: `ariaLabel`
Modal elements must have a title to be accessible. Set that title through the mandatory `ariaLabel` prop.

##### Renamed: Modal.Dialog.Close and Modal.CloseButton
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.

## From version 8.x to 9.0.0

### Core Changes and Removals
Expand Down
137 changes: 77 additions & 60 deletions code/addons/docs/src/blocks/controls/Color.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { ChangeEvent, FC, FocusEvent } from 'react';
import React, { useCallback, useEffect, useMemo, useState } from 'react';

import { Form, TooltipNote, WithTooltip } from 'storybook/internal/components';
import { Button, Form, WithPopover } from 'storybook/internal/components';

import { MarkupIcon } from '@storybook/icons';

Expand All @@ -18,13 +18,6 @@ const Wrapper = styled.div({
maxWidth: 250,
});

const PickerTooltip = styled(WithTooltip)({
position: 'absolute',
zIndex: 1,
top: 4,
left: 4,
});

const TooltipContent = styled.div({
width: 200,
margin: 5,
Expand All @@ -40,10 +33,6 @@ const TooltipContent = styled.div({
},
});

const Note = styled(TooltipNote)(({ theme }) => ({
fontFamily: theme.typography.fonts.base,
}));

const Swatches = styled.div({
display: 'grid',
gridTemplateColumns: 'repeat(9, 16px)',
Expand All @@ -53,22 +42,24 @@ const Swatches = styled.div({
width: 200,
});

const SwatchColor = styled.div<{ active?: boolean }>(({ theme, active }) => ({
width: 16,
height: 16,
boxShadow: active
? `${theme.appBorderColor} 0 0 0 1px inset, ${theme.textMutedColor}50 0 0 0 4px`
: `${theme.appBorderColor} 0 0 0 1px inset`,
borderRadius: theme.appBorderRadius,
}));

const swatchBackground = `url('data:image/svg+xml;charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill-opacity=".05"><path d="M8 0h8v8H8zM0 8h8v8H0z"/></svg>')`;

type SwatchProps = { value: string } & React.ComponentProps<typeof SwatchColor>;
const Swatch = ({ value, style, ...props }: SwatchProps) => {
const backgroundImage = `linear-gradient(${value}, ${value}), ${swatchBackground}, linear-gradient(hsl(0 0 100 / .4), hsl(0 0 100 / .4))`;
return <SwatchColor {...props} style={{ ...style, backgroundImage }} />;
};
const SwatchColor = styled(Button)<{ selected?: boolean; value: string }>(
({ value, selected, theme }) => ({
width: 16,
height: 16,
boxShadow: selected
? `${theme.appBorderColor} 0 0 0 1px inset, ${theme.textMutedColor}50 0 0 0 4px`
: `${theme.appBorderColor} 0 0 0 1px inset`,
border: 'none',
borderRadius: theme.appBorderRadius,
'&, &:hover': {
background: 'unset',
backgroundColor: 'unset',
backgroundImage: `linear-gradient(${value}, ${value}), ${swatchBackground}, linear-gradient(hsl(0 0 100 / .4), hsl(0 0 100 / .4))`,
},
})
);

const Input = styled(Form.Input)(({ theme }) => ({
width: '100%',
Expand All @@ -82,7 +73,15 @@ const Input = styled(Form.Input)(({ theme }) => ({
},
}));

const ToggleIcon = styled(MarkupIcon)(({ theme }) => ({
const PopoverTrigger = styled(SwatchColor)<{ disabled: boolean }>(({ disabled }) => ({
position: 'absolute',
top: 4,
left: 4,
zIndex: 1,
cursor: disabled ? 'not-allowed' : 'pointer',
}));

const CycleColorSpaceButton = styled(Button)(({ theme }) => ({
position: 'absolute',
zIndex: 1,
top: 6,
Expand Down Expand Up @@ -384,12 +383,19 @@ export const ColorControl: FC<ColorControlProps> = ({
<label htmlFor={controlId} className="sb-sr-only">
{name}
</label>
<PickerTooltip
startOpen={startOpen}
trigger={readOnly ? null : undefined}
closeOnOutsideClick
<Input
id={controlId}
value={value}
onChange={(e: ChangeEvent<HTMLInputElement>) => updateValue(e.target.value)}
onFocus={(e: FocusEvent<HTMLInputElement>) => e.target.select()}
readOnly={readOnly}
placeholder="Choose color..."
/>
<WithPopover
defaultVisible={startOpen}
visible={readOnly ? false : undefined}
onVisibleChange={() => color && addPreset(color)}
tooltip={
popover={
<TooltipContent>
<Picker
color={realValue === 'transparent' ? '#000000' : realValue}
Expand All @@ -398,41 +404,52 @@ export const ColorControl: FC<ColorControlProps> = ({
{presets.length > 0 && (
<Swatches>
{presets.map((preset, index: number) => (
<WithTooltip
<SwatchColor
key={`${preset?.value || index}-${index}`}
hasChrome={false}
tooltip={<Note note={preset?.keyword || preset?.value || ''} />}
>
<Swatch
value={preset?.[colorSpace] || ''}
active={
!!(
color &&
preset &&
preset[colorSpace] &&
id(preset[colorSpace] || '') === id(color[colorSpace])
)
}
onClick={() => preset && updateValue(preset.value || '')}
/>
</WithTooltip>
variant="ghost"
padding="small"
size="small"
ariaLabel="Pick this color"
tooltip={preset?.keyword || preset?.value || ''}
value={preset?.value || ''}
selected={
!!(
color &&
preset &&
preset[colorSpace] &&
id(preset[colorSpace] || '') === id(color[colorSpace])
)
}
onClick={() => preset && updateValue(preset.value || '')}
/>
))}
</Swatches>
)}
</TooltipContent>
}
>
<Swatch value={realValue} style={{ margin: 4 }} />
</PickerTooltip>
<Input
id={controlId}
value={value}
onChange={(e: ChangeEvent<HTMLInputElement>) => updateValue(e.target.value)}
onFocus={(e: FocusEvent<HTMLInputElement>) => e.target.select()}
readOnly={readOnly}
placeholder="Choose color..."
/>
{value ? <ToggleIcon onClick={cycleColorSpace} /> : null}
<PopoverTrigger
variant="ghost"
padding="small"
size="small"
ariaLabel="Open color picker"
value={realValue}
style={{ margin: 4 }}
disabled={readOnly}
/>
</WithPopover>
{value ? (
<CycleColorSpaceButton
variant="ghost"
padding="small"
size="small"
ariaLabel="Cycle through color spaces"
disabled={readOnly}
onClick={readOnly ? undefined : cycleColorSpace}
>
<MarkupIcon />
</CycleColorSpaceButton>
) : null}
</Wrapper>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,16 @@ export const IntentSurvey = ({
};

return (
<Modal defaultOpen width={420} onEscapeKeyDown={onDismiss}>
<Modal
ariaLabel="Storybook user survey"
defaultOpen
width={420}
onOpenChange={(isOpen) => {
if (!isOpen) {
onDismiss();
}
}}
>
<Form onSubmit={onSubmitForm} id="intent-survey-form">
<Content>
<Modal.Header>
Expand Down
9 changes: 3 additions & 6 deletions code/addons/vitest/src/components/GlobalErrorModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React, { useContext } from 'react';

import { Button, Modal } from 'storybook/internal/components';

import { CloseIcon, SyncIcon } from '@storybook/icons';
import { SyncIcon } from '@storybook/icons';

import { useStorybookApi } from 'storybook/manager-api';
import { styled } from 'storybook/theming';
Expand Down Expand Up @@ -77,7 +77,6 @@ function ErrorCause({ error }: { error: ErrorLike }) {
export function GlobalErrorModal({ onRerun, storeState }: GlobalErrorModalProps) {
const api = useStorybookApi();
const { isModalOpen, setModalOpen } = useContext(GlobalErrorContext);
const handleClose = () => setModalOpen?.(false);

const troubleshootURL = api.getDocsUrl({
subpath: DOCUMENTATION_FATAL_ERROR_LINK,
Expand Down Expand Up @@ -148,7 +147,7 @@ export function GlobalErrorModal({ onRerun, storeState }: GlobalErrorModalProps)
) : null;

return (
<Modal onEscapeKeyDown={handleClose} onInteractOutside={handleClose} open={isModalOpen}>
<Modal ariaLabel="Storybook Tests error details" onOpenChange={setModalOpen} open={isModalOpen}>
<ModalBar>
<ModalTitle>Storybook Tests error details</ModalTitle>
<ModalActionBar>
Expand All @@ -161,9 +160,7 @@ export function GlobalErrorModal({ onRerun, storeState }: GlobalErrorModalProps)
Troubleshoot
</a>
</Button>
<Button variant="ghost" padding="small" onClick={handleClose} ariaLabel="Close modal">
<CloseIcon />
</Button>
<Modal.Close />
</ModalActionBar>
</ModalBar>
<ModalStackTrace>
Expand Down
4 changes: 1 addition & 3 deletions code/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,6 @@
"@ndelangen/get-tarball": "^3.0.7",
"@ngard/tiny-isequal": "^1.1.0",
"@polka/compression": "^1.0.0-next.28",
"@radix-ui/react-dialog": "^1.1.2",
"@radix-ui/react-scroll-area": "1.2.0-rc.7",
"@radix-ui/react-slot": "^1.0.2",
"@react-aria/utils": "^3.30.1",
Expand All @@ -279,7 +278,6 @@
"@types/pretty-hrtime": "^1.0.0",
"@types/prompts": "^2.0.9",
"@types/react-syntax-highlighter": "11.0.5",
"@types/react-transition-group": "^4",
"@types/semver": "^7.5.8",
"@types/ws": "^8",
"@vitest/utils": "^3.2.4",
Expand Down Expand Up @@ -348,7 +346,7 @@
"react-stately": "^3.41.0",
"react-syntax-highlighter": "^15.4.5",
"react-textarea-autosize": "^8.3.0",
"react-transition-group": "^4.4.5",
"react-transition-state": "^2.3.1",
"require-from-string": "^2.0.2",
"resolve.exports": "^2.0.3",
"sirv": "^2.0.4",
Expand Down
Loading