Merge branch 'next' of github.com:storybookjs/storybook into jeppe/support-svelte-csf-in-vitest

This commit is contained in:
Jeppe Reinhold 2025-01-08 11:14:35 +01:00
commit 7c95fe08ef
163 changed files with 1539 additions and 1238 deletions

View File

@ -1,3 +1,24 @@
## 8.5.0-beta.7
- Addon Test: Context menu updates - [#30107](https://github.com/storybookjs/storybook/pull/30107), thanks @ghengeveld!
- Storysource Addon: Fix source-loader prettier imports - [#29669](https://github.com/storybookjs/storybook/pull/29669), thanks @slax57!
- Vue: Extend sourceDecorator to support v-bind and nested keys in slots - [#28787](https://github.com/storybookjs/storybook/pull/28787), thanks @JoCa96!
## 8.5.0-beta.6
- Addon Test: Always use installed version of vitest - [#30134](https://github.com/storybookjs/storybook/pull/30134), thanks @kasperpeulen!
- Addon Test: Fix documentation links - [#30128](https://github.com/storybookjs/storybook/pull/30128), thanks @yannbf!
- Addon Test: Use correct vitest config file path - [#30135](https://github.com/storybookjs/storybook/pull/30135), thanks @kasperpeulen!
- Automigration: Improve addon-a11y-addon-test - [#30127](https://github.com/storybookjs/storybook/pull/30127), thanks @valentinpalkovic!
## 8.5.0-beta.5
- Addon Test: Only reset story count on file change when watch mode is enabled - [#30121](https://github.com/storybookjs/storybook/pull/30121), thanks @ghengeveld!
- Build: Revert Downgrade to esbuild 0.24.0 - [#30120](https://github.com/storybookjs/storybook/pull/30120), thanks @yannbf!
- Core: Fix `ERR_PACKAGE_PATH_NOT_EXPORTED` in `@storybook/node-logger` - [#30093](https://github.com/storybookjs/storybook/pull/30093), thanks @JReinhold!
- React: Use Act wrapper in Storybook for component rendering - [#30037](https://github.com/storybookjs/storybook/pull/30037), thanks @valentinpalkovic!
- Vite: Add extra entries to `optimizeDeps` - [#30117](https://github.com/storybookjs/storybook/pull/30117), thanks @ndelangen!
## 8.5.0-beta.4
- Addon Themes: Deprecate useThemeParameters - [#30111](https://github.com/storybookjs/storybook/pull/30111), thanks @yannbf!

View File

@ -4,6 +4,7 @@ import type { StorybookConfig } from '../frameworks/react-vite';
const componentsPath = join(__dirname, '../core/src/components');
const managerApiPath = join(__dirname, '../core/src/manager-api');
const imageContextPath = join(__dirname, '..//frameworks/nextjs/src/image-context.ts');
const config: StorybookConfig = {
stories: [
@ -146,6 +147,7 @@ const config: StorybookConfig = {
'storybook/internal/components': componentsPath,
'@storybook/manager-api': managerApiPath,
'storybook/internal/manager-api': managerApiPath,
'sb-original/image-context': imageContextPath,
}
: {}),
},

Binary file not shown.

Before

Width:  |  Height:  |  Size: 175 KiB

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-a11y",
"version": "8.5.0-beta.4",
"version": "8.5.0-beta.7",
"description": "Test component compliance with web accessibility standards",
"keywords": [
"a11y",

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-actions",
"version": "8.5.0-beta.4",
"version": "8.5.0-beta.7",
"description": "Get UI feedback when an action is performed on an interactive element",
"keywords": [
"storybook",

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-backgrounds",
"version": "8.5.0-beta.4",
"version": "8.5.0-beta.7",
"description": "Switch backgrounds to view components in different settings",
"keywords": [
"addon",

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-controls",
"version": "8.5.0-beta.4",
"version": "8.5.0-beta.7",
"description": "Interact with component inputs dynamically in the Storybook UI",
"keywords": [
"addon",

View File

@ -18,7 +18,7 @@ Storybook Docs automatically generates props tables for components in supported
## Usage
For framework-specific setup instructions, see the framework's README: [React](../react/README.md), [Vue3 ](../vue3/README.md), [Angular](../angular/README.md), [Web Components](../web-components/README.md), [Ember](../ember/README.md).
For framework-specific setup instructions, see the framework's README: [React](../react/README.md), [Vue3](../vue3/README.md), [Angular](../angular/README.md), [Web Components](../web-components/README.md), [Ember](../ember/README.md).
### DocsPage

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-docs",
"version": "8.5.0-beta.4",
"version": "8.5.0-beta.7",
"description": "Document component usage and properties in Markdown",
"keywords": [
"addon",

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-essentials",
"version": "8.5.0-beta.4",
"version": "8.5.0-beta.7",
"description": "Curated addons to bring out the best of Storybook",
"keywords": [
"addon",

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-mdx-gfm",
"version": "8.5.0-beta.4",
"version": "8.5.0-beta.7",
"description": "GitHub Flavored Markdown in Storybook",
"keywords": [
"addon",

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-highlight",
"version": "8.5.0-beta.4",
"version": "8.5.0-beta.7",
"description": "Highlight DOM nodes within your stories",
"keywords": [
"storybook-addons",

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-interactions",
"version": "8.5.0-beta.4",
"version": "8.5.0-beta.7",
"description": "Automate, test and debug user interactions",
"keywords": [
"storybook-addons",

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-jest",
"version": "8.5.0-beta.4",
"version": "8.5.0-beta.7",
"description": "React storybook addon that show component jest report",
"keywords": [
"addon",

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-links",
"version": "8.5.0-beta.4",
"version": "8.5.0-beta.7",
"description": "Link stories together to build demos and prototypes with your UI components",
"keywords": [
"storybook-addons",

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-measure",
"version": "8.5.0-beta.4",
"version": "8.5.0-beta.7",
"description": "Inspect layouts by visualizing the box model",
"keywords": [
"storybook-addons",

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-onboarding",
"version": "8.5.0-beta.4",
"version": "8.5.0-beta.7",
"description": "Storybook Addon Onboarding - Introduces a new onboarding experience",
"keywords": [
"storybook-addons",

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-outline",
"version": "8.5.0-beta.4",
"version": "8.5.0-beta.7",
"description": "Outline all elements with CSS to help with layout placement and alignment",
"keywords": [
"storybook-addons",

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-storysource",
"version": "8.5.0-beta.4",
"version": "8.5.0-beta.7",
"description": "View a storys source code to see how it works and paste into your app",
"keywords": [
"addon",

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/experimental-addon-test",
"version": "8.5.0-beta.4",
"version": "8.5.0-beta.7",
"description": "Integrate Vitest with Storybook",
"keywords": [
"storybook-addons",

View File

@ -1,76 +0,0 @@
import React, {
type FC,
type SyntheticEvent,
useCallback,
useEffect,
useRef,
useState,
} from 'react';
import { Button, ListItem } from 'storybook/internal/components';
import type { TestProviderConfig } from 'storybook/internal/core-events';
import { useStorybookApi } from 'storybook/internal/manager-api';
import { useTheme } from 'storybook/internal/theming';
import { type API_HashEntry, type Addon_TestProviderState } from 'storybook/internal/types';
import { PlayHollowIcon, StopAltHollowIcon } from '@storybook/icons';
import { type Config, type Details, TEST_PROVIDER_ID } from '../constants';
import { Description } from './Description';
import { Title } from './Title';
export const ContextMenuItem: FC<{
context: API_HashEntry;
state: TestProviderConfig & Addon_TestProviderState<Details, Config>;
}> = ({ context, state }) => {
const api = useStorybookApi();
const [isDisabled, setDisabled] = useState(false);
const id = useRef(context.id);
id.current = context.id;
const Icon = state.running ? StopAltHollowIcon : PlayHollowIcon;
useEffect(() => {
setDisabled(false);
}, [state.running]);
const onClick = useCallback(
(event: SyntheticEvent) => {
setDisabled(true);
event.stopPropagation();
if (state.running) {
api.cancelTestProvider(TEST_PROVIDER_ID);
} else {
api.runTestProvider(TEST_PROVIDER_ID, { entryId: id.current });
}
},
[api, state.running]
);
const theme = useTheme();
return (
<div
onClick={(event) => {
// stopPropagation to prevent the parent from closing the context menu, which is the default behavior onClick
event.stopPropagation();
}}
>
<ListItem
title={<Title state={state} />}
center={<Description state={state} />}
right={
<Button
onClick={onClick}
variant="ghost"
padding="small"
disabled={state.crashed || isDisabled}
>
<Icon fill={theme.textMutedColor} />
</Button>
}
/>
</div>
);
};

View File

@ -4,6 +4,7 @@ import { Link as LinkComponent } from 'storybook/internal/components';
import { type TestProviderConfig, type TestProviderState } from 'storybook/internal/core-events';
import { styled } from 'storybook/internal/theming';
import type { TestResultResult } from '../node/reporter';
import { GlobalErrorContext } from './GlobalErrorModal';
import { RelativeTime } from './RelativeTime';
@ -19,11 +20,13 @@ const PositiveText = styled.span(({ theme }) => ({
color: theme.color.positiveText,
}));
interface DescriptionProps extends ComponentProps<typeof Wrapper> {
interface DescriptionProps extends Omit<ComponentProps<typeof Wrapper>, 'results'> {
state: TestProviderConfig & TestProviderState;
entryId?: string;
results?: TestResultResult[];
}
export function Description({ state, ...props }: DescriptionProps) {
export function Description({ state, entryId, results, ...props }: DescriptionProps) {
const isMounted = React.useRef(false);
const [isUpdated, setUpdated] = React.useState(false);
const { setModalOpen } = React.useContext(GlobalErrorContext);
@ -48,15 +51,17 @@ export function Description({ state, ...props }: DescriptionProps) {
description = state.progress
? `Testing... ${state.progress.numPassedTests}/${state.progress.numTotalTests}`
: 'Starting...';
} else if (entryId && results?.length) {
description = `Ran ${results.length} ${results.length === 1 ? 'test' : 'tests'}`;
} else if (state.failed && !errorMessage) {
description = 'Failed';
} else if (state.crashed || (state.failed && errorMessage)) {
description = (
<>
<LinkComponent isButton onClick={() => setModalOpen(true)}>
{state.error?.name || 'View full error'}
</LinkComponent>
</>
description = setModalOpen ? (
<LinkComponent isButton onClick={() => setModalOpen(true)}>
{state.error?.name || 'View full error'}
</LinkComponent>
) : (
state.error?.name || 'Failed'
);
} else if (state.progress?.finishedAt) {
description = (

View File

@ -23,7 +23,7 @@ const MethodCallWrapper = styled.div(() => ({
const RowContainer = styled('div', {
shouldForwardProp: (prop) => !['call', 'pausedAt'].includes(prop.toString()),
})<{ call: Call; pausedAt: Call['id'] }>(
})<{ call: Call; pausedAt: Call['id'] | undefined }>(
({ theme, call }) => ({
position: 'relative',
display: 'flex',
@ -117,6 +117,9 @@ const RowMessage = styled('div')(({ theme }) => ({
export const Exception = ({ exception }: { exception: Call['exception'] }) => {
const filter = useAnsiToHtmlFilter();
if (!exception) {
return null;
}
if (isJestError(exception)) {
return <MatcherResult {...exception} />;
}
@ -187,7 +190,7 @@ export const Interaction = ({
</MethodCallWrapper>
</RowLabel>
<RowActions>
{childCallIds?.length > 0 && (
{(childCallIds?.length ?? 0) > 0 && (
<WithTooltip
hasChrome={false}
tooltip={<Note note={`${isCollapsed ? 'Show' : 'Hide'} interactions`} />}

View File

@ -74,10 +74,10 @@ export const MatcherResult = ({
{lines.flatMap((line: string, index: number) => {
if (line.startsWith('expect(')) {
const received = getParams(line, 7);
const remainderIndex = received && 7 + received.length;
const remainderIndex = received ? 7 + received.length : 0;
const matcher = received && line.slice(remainderIndex).match(/\.(to|last|nth)[A-Z]\w+\(/);
if (matcher) {
const expectedIndex = remainderIndex + matcher.index + matcher[0].length;
const expectedIndex = remainderIndex + (matcher.index ?? 0) + matcher[0].length;
const expected = getParams(line, expectedIndex);
if (expected) {
return [

View File

@ -139,7 +139,7 @@ export const Node = ({
case Object.prototype.hasOwnProperty.call(value, '__class__'):
return <ClassNode {...props} {...value.__class__} />;
case Object.prototype.hasOwnProperty.call(value, '__callId__'):
return <MethodCall call={callsById.get(value.__callId__)} callsById={callsById} />;
return <MethodCall call={callsById?.get(value.__callId__)} callsById={callsById} />;
/* eslint-enable no-underscore-dangle */
case Object.prototype.toString.call(value) === '[object Object]':
@ -418,7 +418,7 @@ export const MethodCall = ({
callsById,
}: {
call?: Call;
callsById: Map<Call['id'], Call>;
callsById?: Map<Call['id'], Call>;
}) => {
// Call might be undefined during initial render, can be safely ignored.
if (!call) {
@ -434,7 +434,7 @@ export const MethodCall = ({
const callId = (elem as CallRef).__callId__;
return [
callId ? (
<MethodCall key={`elem${index}`} call={callsById.get(callId)} callsById={callsById} />
<MethodCall key={`elem${index}`} call={callsById?.get(callId)} callsById={callsById} />
) : (
<span key={`elem${index}`}>{elem as any}</span>
),

View File

@ -22,14 +22,6 @@ import type { API_StatusValue } from '@storybook/types';
import { ADDON_ID, STORYBOOK_ADDON_TEST_CHANNEL, TEST_PROVIDER_ID } from '../constants';
import { InteractionsPanel } from './InteractionsPanel';
interface Interaction extends Call {
status: Call['status'];
childCallIds: Call['id'][];
isHidden: boolean;
isCollapsed: boolean;
toggleCollapsed: () => void;
}
const INITIAL_CONTROL_STATES = {
start: false,
back: false,
@ -60,7 +52,7 @@ export const getInteractions = ({
const childCallMap = new Map<Call['id'], Call['id'][]>();
return log
.map<Call & { isHidden: boolean }>(({ callId, ancestors, status }) => {
.map(({ callId, ancestors, status }) => {
let isHidden = false;
ancestors.forEach((ancestor) => {
if (collapsed.has(ancestor)) {
@ -68,11 +60,12 @@ export const getInteractions = ({
}
childCallMap.set(ancestor, (childCallMap.get(ancestor) || []).concat(callId));
});
return { ...calls.get(callId), status, isHidden };
return { ...calls.get(callId)!, status, isHidden };
})
.map<Interaction>((call) => {
.map((call) => {
const status =
call.status === CallStates.ERROR &&
call.ancestors &&
callsById.get(call.ancestors.slice(-1)[0])?.status === CallStates.ACTIVE
? CallStates.ACTIVE
: call.status;
@ -131,7 +124,7 @@ export const Panel = memo<{ storyId: string }>(function PanelMemoized({ storyId
const calls = useRef<Map<Call['id'], Omit<Call, 'status'>>>(new Map());
const setCall = ({ status, ...call }: Call) => calls.current.set(call.id, call);
const endRef = useRef();
const endRef = useRef<HTMLDivElement>();
useEffect(() => {
let observer: IntersectionObserver;
if (global.IntersectionObserver) {
@ -151,6 +144,7 @@ export const Panel = memo<{ storyId: string }>(function PanelMemoized({ storyId
{
[EVENTS.CALL]: setCall,
[EVENTS.SYNC]: (payload) => {
// @ts-expect-error TODO
set((s) => {
const list = getInteractions({
log: payload.logItems,
@ -214,6 +208,7 @@ export const Panel = memo<{ storyId: string }>(function PanelMemoized({ storyId
);
useEffect(() => {
// @ts-expect-error TODO
set((s) => {
const list = getInteractions({
log: log.current,
@ -250,16 +245,17 @@ export const Panel = memo<{ storyId: string }>(function PanelMemoized({ storyId
const hasException =
!!caughtException ||
!!unhandledErrors ||
// @ts-expect-error TODO
interactions.some((v) => v.status === CallStates.ERROR);
const storyStatus = storyStatuses[storyId]?.[TEST_PROVIDER_ID];
const storyTestStatus = storyStatus?.status;
const browserTestStatus = useMemo<CallStates | null>(() => {
const browserTestStatus = useMemo<CallStates | undefined>(() => {
if (!isPlaying && (interactions.length > 0 || hasException)) {
return hasException ? CallStates.ERROR : CallStates.DONE;
}
return isPlaying ? CallStates.ACTIVE : null;
return isPlaying ? CallStates.ACTIVE : undefined;
}, [isPlaying, interactions, hasException]);
const { testRunId } = storyStatus?.data || {};
@ -315,6 +311,7 @@ export const Panel = memo<{ storyId: string }>(function PanelMemoized({ storyId
unhandledErrors={unhandledErrors}
isPlaying={isPlaying}
pausedAt={pausedAt}
// @ts-expect-error TODO
endRef={endRef}
onScrollToEnd={scrollTarget && scrollToTarget}
/>

View File

@ -1,7 +1,7 @@
import { useEffect, useState } from 'react';
export const RelativeTime = ({ timestamp }: { timestamp?: number }) => {
const [timeAgo, setTimeAgo] = useState(null);
const [timeAgo, setTimeAgo] = useState<number | null>(null);
useEffect(() => {
if (timestamp) {

View File

@ -14,7 +14,7 @@ const StyledBadge = styled.div<StatusBadgeProps>(({ theme, status }) => {
[CallStates.ERROR]: theme.color.negative,
[CallStates.ACTIVE]: theme.color.warning,
[CallStates.WAITING]: theme.color.warning,
}[status];
}[status!];
return {
padding: '4px 6px 4px 8px;',
borderRadius: '4px',
@ -36,7 +36,7 @@ export const StatusBadge: React.FC<StatusBadgeProps> = ({ status }) => {
[CallStates.ERROR]: 'Fail',
[CallStates.ACTIVE]: 'Runs',
[CallStates.WAITING]: 'Runs',
}[status];
}[status!];
return (
<StyledBadge aria-label="Status of the test run" status={status}>
{badgeText}

View File

@ -109,7 +109,7 @@ const RerunButton = styled(StyledIconButton)<
>(({ theme, animating, disabled }) => ({
opacity: disabled ? 0.5 : 1,
svg: {
animation: animating && `${theme.animation.rotate360} 200ms ease-out`,
animation: animating ? `${theme.animation.rotate360} 200ms ease-out` : undefined,
},
}));

View File

@ -1,13 +1,12 @@
import React, { useEffect } from 'react';
import React from 'react';
import { Link } from 'storybook/internal/components';
import { useStorybookApi } from 'storybook/internal/manager-api';
import { styled } from 'storybook/internal/theming';
import type { StoryId } from 'storybook/internal/types';
import { CallStates } from '@storybook/instrumenter';
import { DOCUMENTATION_DISCREPANCY_LINK, STORYBOOK_ADDON_TEST_CHANNEL } from '../constants';
import { DOCUMENTATION_DISCREPANCY_LINK } from '../constants';
const Wrapper = styled.div(({ theme: { color, typography, background } }) => ({
textAlign: 'start',
@ -32,7 +31,7 @@ const Wrapper = styled.div(({ theme: { color, typography, background } }) => ({
}));
interface TestDiscrepancyMessageProps {
browserTestStatus: CallStates;
browserTestStatus?: CallStates;
}
export const TestDiscrepancyMessage = ({ browserTestStatus }: TestDiscrepancyMessageProps) => {

View File

@ -43,7 +43,7 @@ const baseState: TestProviderState<Details, Config> = {
cancellable: true,
cancelling: false,
crashed: false,
error: null,
error: undefined,
failed: false,
running: false,
watching: false,
@ -233,7 +233,7 @@ export const Editing: Story = {
play: async ({ canvasElement }) => {
const screen = within(canvasElement);
screen.getByLabelText(/Open settings/).click();
screen.getByLabelText(/Show settings/).click();
},
};

View File

@ -1,6 +1,12 @@
import React, { type ComponentProps, type FC, useCallback, useMemo, useRef, useState } from 'react';
import { Button, ListItem, ProgressSpinner } from 'storybook/internal/components';
import {
Button,
ListItem,
ProgressSpinner,
TooltipNote,
WithTooltip,
} from 'storybook/internal/components';
import {
TESTING_MODULE_CONFIG_CHANGE,
type TestProviderConfig,
@ -31,7 +37,6 @@ import { type Config, type Details, PANEL_ID } from '../constants';
import { type TestStatus } from '../node/reporter';
import { Description } from './Description';
import { TestStatusIcon } from './TestStatusIcon';
import { Title } from './Title';
const Container = styled.div({
display: 'flex',
@ -52,6 +57,12 @@ const Info = styled.div({
minWidth: 0,
});
const Title = styled.div<{ crashed?: boolean }>(({ crashed, theme }) => ({
fontSize: theme.typography.size.s1,
fontWeight: crashed ? 'bold' : 'normal',
color: crashed ? theme.color.negativeText : theme.color.defaultText,
}));
const Actions = styled.div({
display: 'flex',
gap: 2,
@ -92,7 +103,7 @@ const statusMap: Record<TestStatus, ComponentProps<typeof TestStatusIcon>['statu
warning: 'warning',
passed: 'positive',
skipped: 'unknown',
pending: 'unknown',
pending: 'pending',
};
export const TestProviderRender: FC<
@ -162,7 +173,7 @@ export const TestProviderRender: FC<
: undefined;
const a11ySkippedAmount =
state.running || !state?.details.config?.a11y || !state.config.a11y
state.running || !state?.details.config?.a11y || !state.config?.a11y
? null
: a11yResults?.filter((result) => !result).length;
@ -185,11 +196,7 @@ export const TestProviderRender: FC<
})
.sort((a, b) => statusOrder.indexOf(a.status) - statusOrder.indexOf(b.status));
const status = state.running
? 'unknown'
: state.failed
? 'failed'
: (results[0]?.status ?? 'unknown');
const status = results[0]?.status ?? (state.running ? 'pending' : 'unknown');
const openPanel = (id: string, panelId: string) => {
api.selectStory(id);
@ -201,57 +208,90 @@ export const TestProviderRender: FC<
<Container {...props}>
<Heading>
<Info>
<Title id="testing-module-title" state={state} />
<Description id="testing-module-description" state={state} />
<Title id="testing-module-title" crashed={state.crashed}>
{state.crashed ? 'Local tests failed' : 'Run local tests'}
</Title>
<Description
id="testing-module-description"
state={state}
entryId={entryId}
results={results}
/>
</Info>
<Actions>
<Button
aria-label={`${isEditing ? 'Close' : 'Open'} settings for ${state.name}`}
variant="ghost"
padding="small"
active={isEditing}
disabled={state.running && !isEditing}
onClick={() => setIsEditing(!isEditing)}
>
<EditIcon />
</Button>
{state.watchable && !entryId && (
<Button
aria-label={`${state.watching ? 'Disable' : 'Enable'} watch mode for ${state.name}`}
variant="ghost"
padding="small"
active={state.watching}
onClick={() => api.setTestProviderWatchMode(state.id, !state.watching)}
disabled={state.running || isEditing}
{!entryId && (
<WithTooltip
hasChrome={false}
trigger="hover"
tooltip={<TooltipNote note={`${isEditing ? 'Hide' : 'Show'} settings`} />}
>
<EyeIcon />
</Button>
<Button
aria-label={`${isEditing ? 'Hide' : 'Show'} settings`}
variant="ghost"
padding="small"
active={isEditing}
disabled={state.running && !isEditing}
onClick={() => setIsEditing(!isEditing)}
>
<EditIcon />
</Button>
</WithTooltip>
)}
{!entryId && state.watchable && (
<WithTooltip
hasChrome={false}
trigger="hover"
tooltip={<TooltipNote note={`${state.watching ? 'Disable' : 'Enable'} watch mode`} />}
>
<Button
aria-label={`${state.watching ? 'Disable' : 'Enable'} watch mode`}
variant="ghost"
padding="small"
active={state.watching}
onClick={() => api.setTestProviderWatchMode(state.id, !state.watching)}
disabled={state.running || isEditing}
>
<EyeIcon />
</Button>
</WithTooltip>
)}
{state.runnable && (
<>
{state.running && state.cancellable ? (
<Button
aria-label={`Stop ${state.name}`}
variant="ghost"
padding="none"
onClick={() => api.cancelTestProvider(state.id)}
disabled={state.cancelling}
<WithTooltip
hasChrome={false}
trigger="hover"
tooltip={<TooltipNote note="Stop test run" />}
>
<Progress percentage={state.progress?.percentageCompleted}>
<StopIcon />
</Progress>
</Button>
<Button
aria-label="Stop test run"
variant="ghost"
padding="none"
onClick={() => api.cancelTestProvider(state.id)}
disabled={state.cancelling}
>
<Progress percentage={state.progress?.percentageCompleted}>
<StopIcon />
</Progress>
</Button>
</WithTooltip>
) : (
<Button
aria-label={`Start ${state.name}`}
variant="ghost"
padding="small"
onClick={() => api.runTestProvider(state.id, { entryId })}
disabled={state.running || isEditing}
<WithTooltip
hasChrome={false}
trigger="hover"
tooltip={<TooltipNote note="Start test run" />}
>
<PlayHollowIcon />
</Button>
<Button
aria-label="Start test run"
variant="ghost"
padding="small"
onClick={() => api.runTestProvider(state.id, { entryId })}
disabled={state.running || isEditing}
>
<PlayHollowIcon />
</Button>
</WithTooltip>
)}
</>
)}
@ -266,19 +306,6 @@ export const TestProviderRender: FC<
icon={<PointerHandIcon color={theme.textMutedColor} />}
right={<Checkbox type="checkbox" checked disabled />}
/>
<ListItem
as="label"
title={<ItemTitle enabled={config.coverage}>Coverage</ItemTitle>}
icon={<ShieldIcon color={theme.textMutedColor} />}
right={
<Checkbox
type="checkbox"
checked={state.watching ? false : config.coverage}
disabled={state.watching}
onChange={() => updateConfig({ coverage: !config.coverage })}
/>
}
/>
{isA11yAddon && (
<ListItem
as="label"
@ -293,6 +320,21 @@ export const TestProviderRender: FC<
}
/>
)}
{!entryId && (
<ListItem
as="label"
title={<ItemTitle enabled={config.coverage}>Coverage</ItemTitle>}
icon={<ShieldIcon color={theme.textMutedColor} />}
right={
<Checkbox
type="checkbox"
checked={state.watching ? false : config.coverage}
disabled={state.watching}
onChange={() => updateConfig({ coverage: !config.coverage })}
/>
}
/>
)}
</Extras>
) : (
<Extras>
@ -304,48 +346,23 @@ export const TestProviderRender: FC<
const firstNotPassed = results.find(
(r) => r.status === 'failed' || r.status === 'warning'
);
openPanel(firstNotPassed.storyId, PANEL_ID);
if (firstNotPassed) {
openPanel(firstNotPassed.storyId, PANEL_ID);
}
}
: null
: undefined
}
icon={
state.crashed ? (
<TestStatusIcon status="critical" aria-label="status: crashed" />
) : status === 'unknown' ? (
) : // @ts-expect-error: TODO: Fix types
status === 'unknown' ? (
<TestStatusIcon status="unknown" aria-label="status: unknown" />
) : (
<TestStatusIcon status={statusMap[status]} aria-label={`status: ${status}`} />
)
}
/>
{coverageSummary ? (
<ListItem
title={<ItemTitle enabled={config.coverage}>Coverage</ItemTitle>}
href={'/coverage/index.html'}
// @ts-expect-error ListItem doesn't include all anchor attributes in types, but it is an achor element
target="_blank"
aria-label="Open coverage report"
icon={
<TestStatusIcon
percentage={coverageSummary.percentage}
status={coverageSummary.status}
aria-label={`status: ${coverageSummary.status}`}
/>
}
right={
coverageSummary.percentage ? (
<span aria-label={`${coverageSummary.percentage} percent coverage`}>
{coverageSummary.percentage} %
</span>
) : null
}
/>
) : (
<ListItem
title={<ItemTitle enabled={config.coverage}>Coverage</ItemTitle>}
icon={<TestStatusIcon status="unknown" aria-label={`status: unknown`} />}
/>
)}
{isA11yAddon && (
<ListItem
title={<ItemTitle enabled={config.a11y}>Accessibility {a11ySkippedLabel}</ItemTitle>}
@ -359,14 +376,48 @@ export const TestProviderRender: FC<
(report) => report.status === 'failed' || report.status === 'warning'
)
);
openPanel(firstNotPassed.storyId, A11y_ADDON_PANEL_ID);
if (firstNotPassed) {
openPanel(firstNotPassed.storyId, A11y_ADDON_PANEL_ID);
}
}
: null
: undefined
}
icon={<TestStatusIcon status={a11yStatus} aria-label={`status: ${a11yStatus}`} />}
right={isStoryEntry ? null : a11yNotPassedAmount || null}
/>
)}
{!entryId && (
<>
{coverageSummary ? (
<ListItem
title={<ItemTitle enabled={config.coverage}>Coverage</ItemTitle>}
href={'/coverage/index.html'}
// @ts-expect-error ListItem doesn't include all anchor attributes in types, but it is an achor element
target="_blank"
aria-label="Open coverage report"
icon={
<TestStatusIcon
percentage={coverageSummary.percentage}
status={coverageSummary.status}
aria-label={`status: ${coverageSummary.status}`}
/>
}
right={
coverageSummary.percentage ? (
<span aria-label={`${coverageSummary.percentage} percent coverage`}>
{coverageSummary.percentage} %
</span>
) : null
}
/>
) : (
<ListItem
title={<ItemTitle enabled={config.coverage}>Coverage</ItemTitle>}
icon={<TestStatusIcon status="unknown" aria-label={`status: unknown`} />}
/>
)}
</>
)}
</Extras>
)}
</Container>

View File

@ -16,6 +16,12 @@ export const Unknown: Story = {
},
};
export const Pending: Story = {
args: {
status: 'pending',
},
};
export const Positive: Story = {
args: {
status: 'positive',

View File

@ -1,7 +1,7 @@
import { styled } from 'storybook/internal/theming';
export const TestStatusIcon = styled.div<{
status: 'positive' | 'warning' | 'negative' | 'critical' | 'unknown';
status: 'pending' | 'positive' | 'warning' | 'negative' | 'critical' | 'unknown';
percentage?: number;
}>(
({ percentage }) => ({
@ -13,6 +13,12 @@ export const TestStatusIcon = styled.div<{
: 'var(--status-color)',
borderRadius: '50%',
}),
({ status, theme }) =>
status === 'pending' && {
animation: `${theme.animation.glow} 1.5s ease-in-out infinite`,
'--status-color': theme.color.mediumdark,
'--status-background': `${theme.color.mediumdark}66`,
},
({ status, theme }) =>
status === 'positive' && {
'--status-color': theme.color.positive,

View File

@ -1,21 +0,0 @@
import React, { type ComponentProps } from 'react';
import { type TestProviderConfig, type TestProviderState } from 'storybook/internal/core-events';
import { styled } from 'storybook/internal/theming';
const Wrapper = styled.div<{ crashed?: boolean }>(({ crashed, theme }) => ({
fontSize: theme.typography.size.s1,
fontWeight: crashed ? 'bold' : 'normal',
color: crashed ? theme.color.negativeText : theme.color.defaultText,
}));
export const Title = ({
state,
...props
}: { state: TestProviderConfig & TestProviderState } & ComponentProps<typeof Wrapper>) => {
return (
<Wrapper crashed={state.crashed} {...props}>
{state.crashed || state.failed ? 'Local tests failed' : 'Run local tests'}
</Wrapper>
);
};

View File

@ -12,6 +12,13 @@ export const DOCUMENTATION_FATAL_ERROR_LINK = `${DOCUMENTATION_LINK}#what-happen
export const COVERAGE_DIRECTORY = 'coverage';
export const SUPPORTED_FRAMEWORKS = [
'@storybook/nextjs',
'@storybook/experimental-nextjs-vite',
'@storybook/sveltekit',
];
export const SUPPORTED_RENDERERS = ['@storybook/react', '@storybook/svelte', '@storybook/vue3'];
export interface Config {
coverage: boolean;
a11y: boolean;

View File

@ -38,6 +38,7 @@ addons.register(ADDON_ID, (api) => {
runnable: true,
watchable: true,
name: 'Component tests',
// @ts-expect-error: TODO: Fix types
render: (state) => {
const [isModalOpen, setModalOpen] = useState(false);
return (
@ -55,6 +56,7 @@ addons.register(ADDON_ID, (api) => {
);
},
// @ts-expect-error: TODO: Fix types
sidebarContextMenu: ({ context, state }) => {
if (context.type === 'docs') {
return null;
@ -72,6 +74,7 @@ addons.register(ADDON_ID, (api) => {
);
},
// @ts-expect-error: TODO: Fix types
stateUpdater: (state, update) => {
const updated = {
...state,
@ -89,6 +92,7 @@ addons.register(ADDON_ID, (api) => {
await api.experimental_updateStatus(
TEST_PROVIDER_ID,
Object.fromEntries(
// @ts-expect-error: TODO: Fix types
update.details.testResults.flatMap((testResult) =>
testResult.results
.filter(({ storyId }) => storyId)
@ -113,6 +117,7 @@ addons.register(ADDON_ID, (api) => {
await api.experimental_updateStatus(
'storybook/addon-a11y/test-provider',
Object.fromEntries(
// @ts-expect-error: TODO: Fix types
update.details.testResults.flatMap((testResult) =>
testResult.results
.filter(({ storyId }) => storyId)
@ -143,7 +148,7 @@ addons.register(ADDON_ID, (api) => {
return updated;
},
} as Addon_TestProviderType<Details, Config>);
} satisfies Omit<Addon_TestProviderType<Details, Config>, 'id'>);
}
const filter = ({ state }: Combo) => {
@ -158,7 +163,7 @@ addons.register(ADDON_ID, (api) => {
match: ({ viewMode }) => viewMode === 'story',
render: ({ active }) => {
return (
<AddonPanel active={active}>
<AddonPanel active={!!active}>
<Consumer filter={filter}>{({ storyId }) => <Panel storyId={storyId} />}</Consumer>
</AddonPanel>
);

View File

@ -24,7 +24,7 @@ const MAX_START_TIME = 30000;
const vitestModulePath = join(__dirname, 'node', 'vitest.mjs');
// Events that were triggered before Vitest was ready are queued up and resent once it's ready
const eventQueue: { type: string; args: any[] }[] = [];
const eventQueue: { type: string; args?: any[] }[] = [];
let child: null | ChildProcess;
let ready = false;
@ -87,7 +87,7 @@ const bootTestRunner = async (channel: Channel) => {
if (result.type === 'ready') {
// Resend events that triggered (during) the boot sequence, now that Vitest is ready
while (eventQueue.length) {
const { type, args } = eventQueue.shift();
const { type, args } = eventQueue.shift()!;
child?.send({ type, args, from: 'server' });
}

View File

@ -220,7 +220,7 @@ export class StorybookReporter implements Reporter {
(t) => t.status === 'failed' && t.results.length === 0
);
const reducedTestSuiteFailures = new Set<string>();
const reducedTestSuiteFailures = new Set<string | undefined>();
testSuiteFailures.forEach((t) => {
reducedTestSuiteFailures.add(t.message);
@ -240,7 +240,7 @@ export class StorybookReporter implements Reporter {
message: Array.from(reducedTestSuiteFailures).reduce(
(acc, curr) => `${acc}\n${curr}`,
''
),
)!,
}
: {
name: `${unhandledErrors.length} unhandled error${unhandledErrors?.length > 1 ? 's' : ''}`,

View File

@ -22,10 +22,11 @@ const vitest = vi.hoisted(() => ({
configOverride: {
actualTestNamePattern: undefined,
get testNamePattern() {
return this.actualTestNamePattern;
return this.actualTestNamePattern!;
},
set testNamePattern(value: string) {
setTestNamePattern(value);
// @ts-expect-error Ignore for testing
this.actualTestNamePattern = value;
},
},

View File

@ -88,7 +88,7 @@ export class VitestManager {
try {
await this.vitest.init();
} catch (e) {
} catch (e: any) {
let message = 'Failed to initialize Vitest';
const isV8 = e.message?.includes('@vitest/coverage-v8');
const isIstanbul = e.message?.includes('@vitest/coverage-istanbul');
@ -148,7 +148,7 @@ export class VitestManager {
])) as StoryIndex;
const storyIds = requestStoryIds || Object.keys(index.entries);
return storyIds.map((id) => index.entries[id]).filter((story) => story.type === 'story');
} catch (e) {
} catch (e: any) {
log('Failed to fetch story index: ' + e.message);
return [];
}
@ -316,20 +316,21 @@ export class VitestManager {
const id = slash(file);
this.vitest?.logger.clearHighlightCache(id);
this.updateLastChanged(id);
this.storyCountForCurrentRun = 0;
// when watch mode is disabled, don't trigger any tests (below)
// but still invalidate the cache for the changed file, which is handled above
if (!this.testManager.config.watchMode) {
return;
}
this.storyCountForCurrentRun = 0;
await this.runAffectedTests(file);
}
async registerVitestConfigListener() {
this.vitest?.server?.watcher.on('change', async (file) => {
file = normalize(file);
const isConfig = file === this.vitest.server.config.configFile;
const isConfig = file === this.vitest?.server.config.configFile;
if (isConfig) {
log('Restarting Vitest due to config change');
await this.closeVitest();

View File

@ -25,6 +25,7 @@ import { coerce, satisfies } from 'semver';
import { dedent } from 'ts-dedent';
import { type PostinstallOptions } from '../../../lib/cli-storybook/src/add';
import { SUPPORTED_FRAMEWORKS, SUPPORTED_RENDERERS } from './constants';
import { printError, printInfo, printSuccess, step } from './postinstall-logger';
import { getAddonNames } from './utils';
@ -55,8 +56,7 @@ export default async function postInstall(options: PostinstallOptions) {
const allDeps = await packageManager.getAllDependencies();
// only install these dependencies if they are not already installed
const dependencies = ['vitest', '@vitest/browser', 'playwright'].filter((p) => !allDeps[p]);
const vitestVersionSpecifier =
allDeps.vitest || (await packageManager.getInstalledVersion('vitest'));
const vitestVersionSpecifier = await packageManager.getInstalledVersion('vitest');
const coercedVitestVersion = vitestVersionSpecifier ? coerce(vitestVersionSpecifier) : null;
// if Vitest is installed, we use the same version to keep consistency across Vitest packages
const vitestVersionToInstall = vitestVersionSpecifier ?? 'latest';
@ -106,18 +106,11 @@ export default async function postInstall(options: PostinstallOptions) {
}
}
const annotationsImport = [
'@storybook/nextjs',
'@storybook/experimental-nextjs-vite',
'@storybook/sveltekit',
].includes(info.frameworkPackageName)
const annotationsImport = SUPPORTED_FRAMEWORKS.includes(info.frameworkPackageName)
? info.frameworkPackageName === '@storybook/nextjs'
? '@storybook/experimental-nextjs-vite'
: info.frameworkPackageName
: info.rendererPackageName &&
['@storybook/react', '@storybook/svelte', '@storybook/vue3'].includes(
info.rendererPackageName
)
: info.rendererPackageName && SUPPORTED_RENDERERS.includes(info.rendererPackageName)
? info.rendererPackageName
: null;
@ -152,6 +145,16 @@ export default async function postInstall(options: PostinstallOptions) {
`);
}
const mswVersionSpecifier = await packageManager.getInstalledVersion('msw');
const coercedMswVersion = mswVersionSpecifier ? coerce(mswVersionSpecifier) : null;
if (coercedMswVersion && !satisfies(coercedMswVersion, '>=2.0.0')) {
reasons.push(dedent`
The addon uses Vitest behind the scenes, which supports only version 2 and above of MSW. However, we have detected version ${picocolors.bold(coercedMswVersion.version)} in this project.
Please update the 'msw' package and try again.
`);
}
if (info.frameworkPackageName === '@storybook/nextjs') {
const nextVersion = await packageManager.getInstalledVersion('next');
if (!nextVersion) {
@ -166,6 +169,7 @@ export default async function postInstall(options: PostinstallOptions) {
reasons.unshift(
`Storybook Test's automated setup failed due to the following package incompatibilities:`
);
reasons.push('--------------------------------');
reasons.push(
dedent`
You can fix these issues and rerun the command to reinstall. If you wish to roll back the installation, remove ${picocolors.bold(colors.pink(ADDON_NAME))} from the "addons" array
@ -177,14 +181,14 @@ export default async function postInstall(options: PostinstallOptions) {
reasons.push(
dedent`
Please check the documentation for more information about its requirements and installation:
${picocolors.cyan(`https://storybook.js.org/docs/writing-tests/vitest-plugin`)}
${picocolors.cyan(`https://storybook.js.org/docs/writing-tests/test-addon`)}
`
);
} else {
reasons.push(
dedent`
Fear not, however, you can follow the manual installation process instead at:
${picocolors.cyan(`https://storybook.js.org/docs/writing-tests/vitest-plugin#manual`)}
${picocolors.cyan(`https://storybook.js.org/docs/writing-tests/test-addon#manual-setup`)}
`
);
}
@ -306,7 +310,7 @@ export default async function postInstall(options: PostinstallOptions) {
${colors.gray(vitestSetupFile)}
Please refer to the documentation to complete the setup manually:
${picocolors.cyan(`https://storybook.js.org/docs/writing-tests/vitest-plugin#manual`)}
${picocolors.cyan(`https://storybook.js.org/docs/writing-tests/test-addon#manual-setup`)}
`
);
logger.line(1);
@ -366,7 +370,7 @@ export default async function postInstall(options: PostinstallOptions) {
your existing workspace file automatically, you must do it yourself. This was the last step.
Please refer to the documentation to complete the setup manually:
${picocolors.cyan(`https://storybook.js.org/docs/writing-tests/vitest-plugin#manual`)}
${picocolors.cyan(`https://storybook.js.org/docs/writing-tests/test-addon#manual-setup`)}
`
);
logger.line(1);
@ -382,13 +386,13 @@ export default async function postInstall(options: PostinstallOptions) {
'🚨 Oh no!',
dedent`
You seem to have an existing test configuration in your Vite config file:
${colors.gray(vitestWorkspaceFile || '')}
${colors.gray(viteConfigFile || '')}
I was able to configure most of the addon but could not safely extend
your existing workspace file automatically, you must do it yourself. This was the last step.
Please refer to the documentation to complete the setup manually:
${picocolors.cyan(`https://storybook.js.org/docs/writing-tests/vitest-plugin#manual`)}
${picocolors.cyan(`https://storybook.js.org/docs/writing-tests/test-addon#manual-setup`)}
`
);
logger.line(1);
@ -414,14 +418,14 @@ export default async function postInstall(options: PostinstallOptions) {
import { defineWorkspace } from 'vitest/config';
import { storybookTest } from '@storybook/experimental-addon-test/vitest-plugin';
// More info at: https://storybook.js.org/docs/writing-tests/vitest-plugin
// More info at: https://storybook.js.org/docs/writing-tests/test-addon
export default defineWorkspace([
'${relative(dirname(browserWorkspaceFile), rootConfig)}',
{
extends: '${viteConfigFile ? relative(dirname(browserWorkspaceFile), viteConfigFile) : ''}',
plugins: [
// The plugin will run tests for the stories defined in your Storybook config
// See options at: https://storybook.js.org/docs/writing-tests/vitest-plugin#storybooktest
// See options at: https://storybook.js.org/docs/writing-tests/test-addon#storybooktest
storybookTest({ configDir: '${options.configDir}' }),
],
test: {
@ -454,11 +458,11 @@ export default async function postInstall(options: PostinstallOptions) {
import { defineConfig } from 'vitest/config';
import { storybookTest } from '@storybook/experimental-addon-test/vitest-plugin';
// More info at: https://storybook.js.org/docs/writing-tests/vitest-plugin
// More info at: https://storybook.js.org/docs/writing-tests/test-addon
export default defineConfig({
plugins: [
// The plugin will run tests for the stories defined in your Storybook config
// See options at: https://storybook.js.org/docs/writing-tests/vitest-plugin#storybooktest
// See options at: https://storybook.js.org/docs/writing-tests/test-addon#storybooktest
storybookTest({ configDir: '${options.configDir}' }),
],
test: {
@ -488,7 +492,7 @@ export default async function postInstall(options: PostinstallOptions) {
When using the Vitest extension in your editor, all of your stories will be shown as tests!
Check the documentation for more information about its features and options at:
${picocolors.cyan(`https://storybook.js.org/docs/writing-tests/vitest-plugin`)}
${picocolors.cyan(`https://storybook.js.org/docs/writing-tests/test-addon`)}
`
);
logger.line(1);

View File

@ -74,13 +74,15 @@ export const teardown = async () => {
logger.verbose('Stopping Storybook process');
await new Promise<void>((resolve, reject) => {
// Storybook starts multiple child processes, so we need to kill the whole tree
treeKill(storybookProcess.pid, 'SIGTERM', (error) => {
if (error) {
logger.error('Failed to stop Storybook process:');
reject(error);
return;
}
resolve();
});
if (storybookProcess?.pid) {
treeKill(storybookProcess.pid, 'SIGTERM', (error) => {
if (error) {
logger.error('Failed to stop Storybook process:');
reject(error);
return;
}
resolve();
});
}
});
};

View File

@ -209,6 +209,7 @@ export const storybookTest = async (options?: UserOptions): Promise<Plugin[]> =>
}
: {}),
// @ts-expect-error: TODO
browser: {
...inputConfig_ONLY_MUTATE_WHEN_STRICTLY_NEEDED_OR_YOU_WILL_BE_FIRED.test?.browser,
commands: {
@ -284,9 +285,11 @@ export const storybookTest = async (options?: UserOptions): Promise<Plugin[]> =>
// alert the user of problems
if (
inputConfig_ONLY_MUTATE_WHEN_STRICTLY_NEEDED_OR_YOU_WILL_BE_FIRED.test.include?.length > 0
(inputConfig_ONLY_MUTATE_WHEN_STRICTLY_NEEDED_OR_YOU_WILL_BE_FIRED.test?.include?.length ??
0) > 0
) {
// remove the user's existing include, because we're replacing it with our own heuristic based on main.ts#stories
// @ts-expect-error: Ignore
inputConfig_ONLY_MUTATE_WHEN_STRICTLY_NEEDED_OR_YOU_WILL_BE_FIRED.test.include = [];
console.log(
picocolors.yellow(dedent`
@ -303,19 +306,21 @@ export const storybookTest = async (options?: UserOptions): Promise<Plugin[]> =>
return config;
},
async configureServer(server) {
for (const staticDir of staticDirs) {
try {
const { staticPath, targetEndpoint } = mapStaticDir(staticDir, directories.configDir);
server.middlewares.use(
targetEndpoint,
sirv(staticPath, {
dev: true,
etag: true,
extensions: [],
})
);
} catch (e) {
console.warn(e);
if (staticDirs) {
for (const staticDir of staticDirs) {
try {
const { staticPath, targetEndpoint } = mapStaticDir(staticDir, directories.configDir);
server.middlewares.use(
targetEndpoint,
sirv(staticPath, {
dev: true,
etag: true,
extensions: [],
})
);
} catch (e) {
console.warn(e);
}
}
}
},

View File

@ -85,7 +85,7 @@ export const setViewport = async (parameters: Parameters = {}, globals: Globals
let viewportWidth = DEFAULT_VIEWPORT_DIMENSIONS.width;
let viewportHeight = DEFAULT_VIEWPORT_DIMENSIONS.height;
if (defaultViewport in viewports) {
if (defaultViewport && defaultViewport in viewports) {
const styles = viewports[defaultViewport].styles as ViewportStyles;
if (styles?.width && styles?.height) {
const { width, height } = styles;

View File

@ -5,7 +5,7 @@
"module": "Preserve",
"moduleResolution": "Bundler",
"types": ["vitest"],
"strict": false
"strict": true
},
"include": ["src/**/*", "./typings.d.ts"]
}

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-themes",
"version": "8.5.0-beta.4",
"version": "8.5.0-beta.7",
"description": "Switch between multiple themes for you components in Storybook",
"keywords": [
"css",

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-toolbars",
"version": "8.5.0-beta.4",
"version": "8.5.0-beta.7",
"description": "Create your own toolbar items that control story rendering",
"keywords": [
"addon",

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-viewport",
"version": "8.5.0-beta.4",
"version": "8.5.0-beta.7",
"description": "Build responsive components by adjusting Storybooks viewport size and orientation",
"keywords": [
"addon",

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/builder-vite",
"version": "8.5.0-beta.4",
"version": "8.5.0-beta.7",
"description": "A plugin to run and build Storybooks with Vite",
"homepage": "https://github.com/storybookjs/storybook/tree/next/code/builders/builder-vite/#readme",
"bugs": {

View File

@ -1,13 +1,14 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { describe, expect, it, vi } from 'vitest';
import { toImportFn } from './codegen-importfn-script';
describe('toImportFn', () => {
it('should correctly map story paths to import functions for absolute paths on Linux', async () => {
const root = '/absolute/path';
vi.spyOn(process, 'cwd').mockReturnValue('/absolute/path');
const stories = ['/absolute/path/to/story1.js', '/absolute/path/to/story2.js'];
const result = await toImportFn(root, stories);
const result = await toImportFn(stories);
expect(result).toMatchInlineSnapshot(`
"const importers = {
@ -22,10 +23,10 @@ describe('toImportFn', () => {
});
it('should correctly map story paths to import functions for absolute paths on Windows', async () => {
const root = 'C:\\absolute\\path';
vi.spyOn(process, 'cwd').mockReturnValue('C:\\absolute\\path');
const stories = ['C:\\absolute\\path\\to\\story1.js', 'C:\\absolute\\path\\to\\story2.js'];
const result = await toImportFn(root, stories);
const result = await toImportFn(stories);
expect(result).toMatchInlineSnapshot(`
"const importers = {
@ -43,7 +44,7 @@ describe('toImportFn', () => {
const root = '/absolute/path';
const stories: string[] = [];
const result = await toImportFn(root, stories);
const result = await toImportFn(stories);
expect(result).toMatchInlineSnapshot(`
"const importers = {};

View File

@ -29,9 +29,9 @@ function toImportPath(relativePath: string) {
*
* @param stories An array of absolute story paths.
*/
export async function toImportFn(root: string, stories: string[]) {
export async function toImportFn(stories: string[]) {
const objectEntries = stories.map((file) => {
const relativePath = relative(root, file);
const relativePath = relative(process.cwd(), file);
return [toImportPath(relativePath), genDynamicImport(normalize(file))] as [string, string];
});
@ -51,5 +51,5 @@ export async function generateImportFnScriptCode(options: Options): Promise<stri
// We can then call toImportFn to create a function that can be used to load each story dynamically.
// eslint-disable-next-line @typescript-eslint/return-await
return await toImportFn(options.projectRoot || process.cwd(), stories);
return await toImportFn(stories);
}

View File

@ -13,16 +13,58 @@ const INCLUDE_CANDIDATES = [
'@emotion/core',
'@emotion/is-prop-valid',
'@emotion/styled',
'@storybook/addon-a11y/preview',
'@storybook/addon-backgrounds/preview',
'@storybook/addon-designs/blocks',
'@storybook/addon-docs/preview',
'@storybook/addon-essentials/actions/preview',
'@storybook/addon-essentials/actions/preview',
'@storybook/addon-essentials/backgrounds/preview',
'@storybook/addon-essentials/docs/preview',
'@storybook/addon-essentials/highlight/preview',
'@storybook/addon-essentials/measure/preview',
'@storybook/addon-essentials/outline/preview',
'@storybook/addon-essentials/viewport/preview',
'@storybook/addon-highlight/preview',
'@storybook/addon-links/preview',
'@storybook/addon-measure/preview',
'@storybook/addon-outline/preview',
'@storybook/addon-themes',
'@storybook/addon-themes/preview',
'@storybook/addon-viewport',
'@storybook/addon-viewport/preview',
'@storybook/blocks',
'@storybook/components',
'@storybook/experimental-addon-test/preview',
'@storybook/experimental-nextjs-vite/dist/preview.mjs',
'@storybook/html',
'@storybook/html/dist/entry-preview-docs.mjs',
'@storybook/html/dist/entry-preview.mjs',
'@storybook/preact',
'@storybook/preact/dist/entry-preview-docs.mjs',
'@storybook/preact/dist/entry-preview.mjs',
'@storybook/react > acorn-jsx',
'@storybook/react',
'@storybook/react/dist/entry-preview-docs.mjs',
'@storybook/react/dist/entry-preview-rsc.mjs',
'@storybook/react/dist/entry-preview.mjs',
'@storybook/svelte',
'@storybook/svelte/dist/entry-preview-docs.mjs',
'@storybook/svelte/dist/entry-preview.mjs',
'@storybook/theming',
'@storybook/vue3',
'@storybook/vue3/dist/entry-preview-docs.mjs',
'@storybook/vue3/dist/entry-preview.mjs',
'@storybook/web-components',
'@storybook/web-components/dist/entry-preview-docs.mjs',
'@storybook/web-components/dist/entry-preview.mjs',
'acorn-jsx',
'acorn-walk',
'acorn',
'airbnb-js-shims',
'ansi-to-html',
'axe-core',
'chromatic/isChromatic',
'color-convert',
'deep-object-diff',
'doctrine',
@ -73,6 +115,8 @@ const INCLUDE_CANDIDATES = [
'lodash/upperFirst.js',
'lodash/upperFirst',
'memoizerific',
'mockdate',
'msw-storybook-addon',
'overlayscrollbars',
'polished',
'prettier/parser-babel',
@ -100,8 +144,11 @@ const INCLUDE_CANDIDATES = [
'refractor/lang/typescript.js',
'refractor/lang/yaml.js',
'regenerator-runtime/runtime.js',
'sb-original/default-loader',
'sb-original/image-context',
'slash',
'store2',
'storybook/internal/preview/runtime',
'synchronous-promise',
'telejson',
'ts-dedent',

View File

@ -53,18 +53,18 @@ export async function commonConfig(
const { viteConfigPath } = await getBuilderOptions<BuilderOptions>(options);
options.projectRoot = options.projectRoot || resolve(options.configDir, '..');
const projectRoot = resolve(options.configDir, '..');
// I destructure away the `build` property from the user's config object
// I do this because I can contain config that breaks storybook, such as we had in a lit project.
// If the user needs to configure the `build` they need to do so in the viteFinal function in main.js.
const { config: { build: buildProperty = undefined, ...userConfig } = {} } =
(await loadConfigFromFile(configEnv, viteConfigPath, options.projectRoot)) ?? {};
(await loadConfigFromFile(configEnv, viteConfigPath, projectRoot)) ?? {};
const sbConfig: InlineConfig = {
configFile: false,
cacheDir: resolvePathInStorybookCache('sb-vite', options.cacheKey),
root: options.projectRoot,
root: projectRoot,
// Allow storybook deployed as subfolder. See https://github.com/storybookjs/builder-vite/issues/238
base: './',
plugins: await pluginConfig(options),

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/builder-webpack5",
"version": "8.5.0-beta.4",
"version": "8.5.0-beta.7",
"description": "Storybook framework-agnostic API",
"keywords": [
"storybook"
@ -64,7 +64,6 @@
},
"dependencies": {
"@storybook/core-webpack": "workspace:*",
"@types/node": "^22.0.0",
"@types/semver": "^7.3.4",
"browser-assert": "^1.2.1",
"case-sensitive-paths-webpack-plugin": "^2.4.0",
@ -90,6 +89,7 @@
"webpack-virtual-modules": "^0.6.0"
},
"devDependencies": {
"@types/node": "^22.0.0",
"@types/pretty-hrtime": "^1.0.0",
"@types/terser-webpack-plugin": "^5.2.0",
"@types/webpack-hot-middleware": "^2.25.6",

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/core",
"version": "8.5.0-beta.4",
"version": "8.5.0-beta.7",
"description": "Storybook framework-agnostic API",
"keywords": [
"storybook"

View File

@ -1,88 +1,88 @@
// auto generated file, do not edit
export default {
'@storybook/addon-a11y': '8.5.0-beta.4',
'@storybook/addon-actions': '8.5.0-beta.4',
'@storybook/addon-backgrounds': '8.5.0-beta.4',
'@storybook/addon-controls': '8.5.0-beta.4',
'@storybook/addon-docs': '8.5.0-beta.4',
'@storybook/addon-essentials': '8.5.0-beta.4',
'@storybook/addon-mdx-gfm': '8.5.0-beta.4',
'@storybook/addon-highlight': '8.5.0-beta.4',
'@storybook/addon-interactions': '8.5.0-beta.4',
'@storybook/addon-jest': '8.5.0-beta.4',
'@storybook/addon-links': '8.5.0-beta.4',
'@storybook/addon-measure': '8.5.0-beta.4',
'@storybook/addon-onboarding': '8.5.0-beta.4',
'@storybook/addon-outline': '8.5.0-beta.4',
'@storybook/addon-storysource': '8.5.0-beta.4',
'@storybook/experimental-addon-test': '8.5.0-beta.4',
'@storybook/addon-themes': '8.5.0-beta.4',
'@storybook/addon-toolbars': '8.5.0-beta.4',
'@storybook/addon-viewport': '8.5.0-beta.4',
'@storybook/builder-vite': '8.5.0-beta.4',
'@storybook/builder-webpack5': '8.5.0-beta.4',
'@storybook/core': '8.5.0-beta.4',
'@storybook/builder-manager': '8.5.0-beta.4',
'@storybook/channels': '8.5.0-beta.4',
'@storybook/client-logger': '8.5.0-beta.4',
'@storybook/components': '8.5.0-beta.4',
'@storybook/core-common': '8.5.0-beta.4',
'@storybook/core-events': '8.5.0-beta.4',
'@storybook/core-server': '8.5.0-beta.4',
'@storybook/csf-tools': '8.5.0-beta.4',
'@storybook/docs-tools': '8.5.0-beta.4',
'@storybook/manager': '8.5.0-beta.4',
'@storybook/manager-api': '8.5.0-beta.4',
'@storybook/node-logger': '8.5.0-beta.4',
'@storybook/preview': '8.5.0-beta.4',
'@storybook/preview-api': '8.5.0-beta.4',
'@storybook/router': '8.5.0-beta.4',
'@storybook/telemetry': '8.5.0-beta.4',
'@storybook/theming': '8.5.0-beta.4',
'@storybook/types': '8.5.0-beta.4',
'@storybook/angular': '8.5.0-beta.4',
'@storybook/ember': '8.5.0-beta.4',
'@storybook/experimental-nextjs-vite': '8.5.0-beta.4',
'@storybook/html-vite': '8.5.0-beta.4',
'@storybook/html-webpack5': '8.5.0-beta.4',
'@storybook/nextjs': '8.5.0-beta.4',
'@storybook/preact-vite': '8.5.0-beta.4',
'@storybook/preact-webpack5': '8.5.0-beta.4',
'@storybook/react-native-web-vite': '8.5.0-beta.4',
'@storybook/react-vite': '8.5.0-beta.4',
'@storybook/react-webpack5': '8.5.0-beta.4',
'@storybook/server-webpack5': '8.5.0-beta.4',
'@storybook/svelte-vite': '8.5.0-beta.4',
'@storybook/svelte-webpack5': '8.5.0-beta.4',
'@storybook/sveltekit': '8.5.0-beta.4',
'@storybook/vue3-vite': '8.5.0-beta.4',
'@storybook/vue3-webpack5': '8.5.0-beta.4',
'@storybook/web-components-vite': '8.5.0-beta.4',
'@storybook/web-components-webpack5': '8.5.0-beta.4',
'@storybook/blocks': '8.5.0-beta.4',
storybook: '8.5.0-beta.4',
sb: '8.5.0-beta.4',
'@storybook/cli': '8.5.0-beta.4',
'@storybook/codemod': '8.5.0-beta.4',
'@storybook/core-webpack': '8.5.0-beta.4',
'create-storybook': '8.5.0-beta.4',
'@storybook/csf-plugin': '8.5.0-beta.4',
'@storybook/instrumenter': '8.5.0-beta.4',
'@storybook/react-dom-shim': '8.5.0-beta.4',
'@storybook/source-loader': '8.5.0-beta.4',
'@storybook/test': '8.5.0-beta.4',
'@storybook/preset-create-react-app': '8.5.0-beta.4',
'@storybook/preset-html-webpack': '8.5.0-beta.4',
'@storybook/preset-preact-webpack': '8.5.0-beta.4',
'@storybook/preset-react-webpack': '8.5.0-beta.4',
'@storybook/preset-server-webpack': '8.5.0-beta.4',
'@storybook/preset-svelte-webpack': '8.5.0-beta.4',
'@storybook/preset-vue3-webpack': '8.5.0-beta.4',
'@storybook/html': '8.5.0-beta.4',
'@storybook/preact': '8.5.0-beta.4',
'@storybook/react': '8.5.0-beta.4',
'@storybook/server': '8.5.0-beta.4',
'@storybook/svelte': '8.5.0-beta.4',
'@storybook/vue3': '8.5.0-beta.4',
'@storybook/web-components': '8.5.0-beta.4',
'@storybook/addon-a11y': '8.5.0-beta.7',
'@storybook/addon-actions': '8.5.0-beta.7',
'@storybook/addon-backgrounds': '8.5.0-beta.7',
'@storybook/addon-controls': '8.5.0-beta.7',
'@storybook/addon-docs': '8.5.0-beta.7',
'@storybook/addon-essentials': '8.5.0-beta.7',
'@storybook/addon-mdx-gfm': '8.5.0-beta.7',
'@storybook/addon-highlight': '8.5.0-beta.7',
'@storybook/addon-interactions': '8.5.0-beta.7',
'@storybook/addon-jest': '8.5.0-beta.7',
'@storybook/addon-links': '8.5.0-beta.7',
'@storybook/addon-measure': '8.5.0-beta.7',
'@storybook/addon-onboarding': '8.5.0-beta.7',
'@storybook/addon-outline': '8.5.0-beta.7',
'@storybook/addon-storysource': '8.5.0-beta.7',
'@storybook/experimental-addon-test': '8.5.0-beta.7',
'@storybook/addon-themes': '8.5.0-beta.7',
'@storybook/addon-toolbars': '8.5.0-beta.7',
'@storybook/addon-viewport': '8.5.0-beta.7',
'@storybook/builder-vite': '8.5.0-beta.7',
'@storybook/builder-webpack5': '8.5.0-beta.7',
'@storybook/core': '8.5.0-beta.7',
'@storybook/builder-manager': '8.5.0-beta.7',
'@storybook/channels': '8.5.0-beta.7',
'@storybook/client-logger': '8.5.0-beta.7',
'@storybook/components': '8.5.0-beta.7',
'@storybook/core-common': '8.5.0-beta.7',
'@storybook/core-events': '8.5.0-beta.7',
'@storybook/core-server': '8.5.0-beta.7',
'@storybook/csf-tools': '8.5.0-beta.7',
'@storybook/docs-tools': '8.5.0-beta.7',
'@storybook/manager': '8.5.0-beta.7',
'@storybook/manager-api': '8.5.0-beta.7',
'@storybook/node-logger': '8.5.0-beta.7',
'@storybook/preview': '8.5.0-beta.7',
'@storybook/preview-api': '8.5.0-beta.7',
'@storybook/router': '8.5.0-beta.7',
'@storybook/telemetry': '8.5.0-beta.7',
'@storybook/theming': '8.5.0-beta.7',
'@storybook/types': '8.5.0-beta.7',
'@storybook/angular': '8.5.0-beta.7',
'@storybook/ember': '8.5.0-beta.7',
'@storybook/experimental-nextjs-vite': '8.5.0-beta.7',
'@storybook/html-vite': '8.5.0-beta.7',
'@storybook/html-webpack5': '8.5.0-beta.7',
'@storybook/nextjs': '8.5.0-beta.7',
'@storybook/preact-vite': '8.5.0-beta.7',
'@storybook/preact-webpack5': '8.5.0-beta.7',
'@storybook/react-native-web-vite': '8.5.0-beta.7',
'@storybook/react-vite': '8.5.0-beta.7',
'@storybook/react-webpack5': '8.5.0-beta.7',
'@storybook/server-webpack5': '8.5.0-beta.7',
'@storybook/svelte-vite': '8.5.0-beta.7',
'@storybook/svelte-webpack5': '8.5.0-beta.7',
'@storybook/sveltekit': '8.5.0-beta.7',
'@storybook/vue3-vite': '8.5.0-beta.7',
'@storybook/vue3-webpack5': '8.5.0-beta.7',
'@storybook/web-components-vite': '8.5.0-beta.7',
'@storybook/web-components-webpack5': '8.5.0-beta.7',
'@storybook/blocks': '8.5.0-beta.7',
storybook: '8.5.0-beta.7',
sb: '8.5.0-beta.7',
'@storybook/cli': '8.5.0-beta.7',
'@storybook/codemod': '8.5.0-beta.7',
'@storybook/core-webpack': '8.5.0-beta.7',
'create-storybook': '8.5.0-beta.7',
'@storybook/csf-plugin': '8.5.0-beta.7',
'@storybook/instrumenter': '8.5.0-beta.7',
'@storybook/react-dom-shim': '8.5.0-beta.7',
'@storybook/source-loader': '8.5.0-beta.7',
'@storybook/test': '8.5.0-beta.7',
'@storybook/preset-create-react-app': '8.5.0-beta.7',
'@storybook/preset-html-webpack': '8.5.0-beta.7',
'@storybook/preset-preact-webpack': '8.5.0-beta.7',
'@storybook/preset-react-webpack': '8.5.0-beta.7',
'@storybook/preset-server-webpack': '8.5.0-beta.7',
'@storybook/preset-svelte-webpack': '8.5.0-beta.7',
'@storybook/preset-vue3-webpack': '8.5.0-beta.7',
'@storybook/html': '8.5.0-beta.7',
'@storybook/preact': '8.5.0-beta.7',
'@storybook/react': '8.5.0-beta.7',
'@storybook/server': '8.5.0-beta.7',
'@storybook/svelte': '8.5.0-beta.7',
'@storybook/vue3': '8.5.0-beta.7',
'@storybook/web-components': '8.5.0-beta.7',
};

View File

@ -1 +1 @@
export const version = '8.5.0-beta.4';
export const version = '8.5.0-beta.7';

View File

@ -22,6 +22,7 @@ const empty = {
const PositionedWithTooltip = styled(WithTooltip)({
position: 'absolute',
right: 0,
zIndex: 1,
});
const FloatingStatusButton = styled(StatusButton)({

View File

@ -1,6 +1,6 @@
import React from 'react';
import { Button, ProgressSpinner } from '@storybook/core/components';
import { Button, ProgressSpinner, TooltipNote, WithTooltip } from '@storybook/core/components';
import { styled } from '@storybook/core/theming';
import { EyeIcon, PlayHollowIcon, StopAltIcon } from '@storybook/icons';
@ -61,46 +61,68 @@ export const LegacyRender = ({ ...state }: TestProviders[keyof TestProviders]) =
<Actions>
{state.watchable && (
<Button
aria-label={`${state.watching ? 'Disable' : 'Enable'} watch mode for ${name}`}
variant="ghost"
padding="small"
active={state.watching}
onClick={() => api.setTestProviderWatchMode(state.id, !state.watching)}
disabled={state.crashed || state.running}
<WithTooltip
hasChrome={false}
trigger="hover"
tooltip={
<TooltipNote
note={`${state.watching ? 'Disable' : 'Enable'} watch mode for ${state.name}`}
/>
}
>
<EyeIcon />
</Button>
<Button
aria-label={`${state.watching ? 'Disable' : 'Enable'} watch mode for ${state.name}`}
variant="ghost"
padding="small"
active={state.watching}
onClick={() => api.setTestProviderWatchMode(state.id, !state.watching)}
disabled={state.crashed || state.running}
>
<EyeIcon />
</Button>
</WithTooltip>
)}
{state.runnable && (
<>
{state.running && state.cancellable ? (
<Button
aria-label={`Stop ${name}`}
variant="ghost"
padding="none"
onClick={() => api.cancelTestProvider(state.id)}
disabled={state.cancelling}
<WithTooltip
hasChrome={false}
trigger="hover"
tooltip={<TooltipNote note={`Stop ${state.name}`} />}
>
<Progress
percentage={
state.progress?.percentageCompleted ??
(state.details as any)?.buildProgressPercentage
}
<Button
aria-label={`Stop ${state.name}`}
variant="ghost"
padding="none"
onClick={() => api.cancelTestProvider(state.id)}
disabled={state.cancelling}
>
<StopIcon />
</Progress>
</Button>
<Progress
percentage={
state.progress?.percentageCompleted ??
(state.details as any)?.buildProgressPercentage
}
>
<StopIcon />
</Progress>
</Button>
</WithTooltip>
) : (
<Button
aria-label={`Start ${state.name}`}
variant="ghost"
padding="small"
onClick={() => api.runTestProvider(state.id)}
disabled={state.crashed || state.running}
<WithTooltip
hasChrome={false}
trigger="hover"
tooltip={<TooltipNote note={`Start ${state.name}`} />}
>
<PlayHollowIcon />
</Button>
<Button
aria-label={`Start ${state.name}`}
variant="ghost"
padding="small"
onClick={() => api.runTestProvider(state.id)}
disabled={state.crashed || state.running}
>
<PlayHollowIcon />
</Button>
</WithTooltip>
)}
</>
)}

View File

@ -1,11 +1,10 @@
import React, { type FC, Fragment, useEffect, useState } from 'react';
import React, { type FC, useEffect, useState } from 'react';
import { Addon_TypesEnum } from '@storybook/core/types';
import type { Meta, StoryObj } from '@storybook/react';
import { expect, fn, waitFor, within } from '@storybook/test';
import { expect, fireEvent, fn, waitFor, within } from '@storybook/test';
import { type API, ManagerContext } from '@storybook/core/manager-api';
import { userEvent } from '@storybook/testing-library';
import { SidebarBottomBase } from './SidebarBottom';
@ -156,18 +155,18 @@ export const DynamicHeight: StoryObj = {
const screen = await within(canvasElement);
const toggleButton = await screen.getByLabelText(/Expand/);
await userEvent.click(toggleButton);
await fireEvent.click(toggleButton);
const content = await screen.findByText('CUSTOM CONTENT WITH DYNAMIC HEIGHT');
const collapse = await screen.getByTestId('collapse');
await expect(content).toBeVisible();
await userEvent.click(toggleButton);
await fireEvent.click(toggleButton);
await waitFor(() => expect(collapse.getBoundingClientRect()).toHaveProperty('height', 0));
await userEvent.click(toggleButton);
await fireEvent.click(toggleButton);
await waitFor(() => expect(collapse.getBoundingClientRect()).not.toHaveProperty('height', 0));
},

View File

@ -56,12 +56,14 @@ const Content = styled.div(({ theme }) => ({
bottom: 0,
left: 0,
right: 0,
padding: 12,
padding: '12px 0',
margin: '0 12px',
display: 'flex',
flexDirection: 'column',
gap: 12,
color: theme.color.defaultText,
fontSize: theme.typography.size.s1,
overflow: 'hidden',
'&:empty': {
display: 'none',

View File

@ -274,21 +274,31 @@ export const TestingModule = ({
)}
<Filters>
{hasTestProviders && (
<CollapseToggle
variant="ghost"
padding="small"
onClick={toggleCollapsed}
id="testing-module-collapse-toggle"
aria-label={isCollapsed ? 'Expand testing module' : 'Collapse testing module'}
<WithTooltip
hasChrome={false}
tooltip={
<TooltipNote
note={isCollapsed ? 'Expand testing module' : 'Collapse testing module'}
/>
}
trigger="hover"
>
<ChevronSmallUpIcon
style={{
transform: isCollapsed ? 'none' : 'rotate(180deg)',
transition: 'transform 250ms',
willChange: 'auto',
}}
/>
</CollapseToggle>
<CollapseToggle
variant="ghost"
padding="small"
onClick={toggleCollapsed}
id="testing-module-collapse-toggle"
aria-label={isCollapsed ? 'Expand testing module' : 'Collapse testing module'}
>
<ChevronSmallUpIcon
style={{
transform: isCollapsed ? 'none' : 'rotate(180deg)',
transition: 'transform 250ms',
willChange: 'auto',
}}
/>
</CollapseToggle>
</WithTooltip>
)}
{errorCount > 0 && (

View File

@ -16,7 +16,7 @@ const createPanelActions = memoize(1)((api) => ({
togglePosition: () => api.togglePanelPosition(),
}));
const getPanels = (api: API) => {
const getPanels = memoize(1)((api: API) => {
const allPanels = api.getElements(Addon_TypesEnum.PANEL);
const story = api.getCurrentStoryData();
@ -42,7 +42,7 @@ const getPanels = (api: API) => {
});
return filteredPanels;
};
});
const mapper = ({ state, api }: Combo) => ({
panels: getPanels(api),

View File

@ -195,7 +195,6 @@ export interface BuilderOptions {
ignorePreview?: boolean;
cache?: FileSystemCache;
configDir: string;
projectRoot?: string;
docsMode?: boolean;
features?: StorybookConfigRaw['features'];
versionCheck?: VersionCheck;

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/builder-manager",
"version": "8.5.0-beta.4",
"version": "8.5.0-beta.7",
"description": "Storybook manager builder",
"keywords": [
"storybook"

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/channels",
"version": "8.5.0-beta.4",
"version": "8.5.0-beta.7",
"description": "",
"keywords": [
"storybook"

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/client-logger",
"version": "8.5.0-beta.4",
"version": "8.5.0-beta.7",
"description": "",
"keywords": [
"storybook"

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/components",
"version": "8.5.0-beta.4",
"version": "8.5.0-beta.7",
"description": "Core Storybook Components",
"keywords": [
"storybook"

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/core-common",
"version": "8.5.0-beta.4",
"version": "8.5.0-beta.7",
"description": "Storybook framework-agnostic API",
"keywords": [
"storybook"

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/core-events",
"version": "8.5.0-beta.4",
"version": "8.5.0-beta.7",
"description": "Event names used in storybook core",
"keywords": [
"storybook"

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/core-server",
"version": "8.5.0-beta.4",
"version": "8.5.0-beta.7",
"description": "Storybook framework-agnostic API",
"keywords": [
"storybook"

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/csf-tools",
"version": "8.5.0-beta.4",
"version": "8.5.0-beta.7",
"description": "Parse and manipulate CSF and Storybook config files",
"keywords": [
"storybook"

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/docs-tools",
"version": "8.5.0-beta.4",
"version": "8.5.0-beta.7",
"description": "Shared utility functions for frameworks to implement docs",
"keywords": [
"storybook"

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/manager-api",
"version": "8.5.0-beta.4",
"version": "8.5.0-beta.7",
"description": "Core Storybook Manager API & Context",
"keywords": [
"storybook"

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/manager",
"version": "8.5.0-beta.4",
"version": "8.5.0-beta.7",
"description": "Core Storybook UI",
"keywords": [
"storybook"

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/node-logger",
"version": "8.5.0-beta.4",
"version": "8.5.0-beta.7",
"description": "",
"keywords": [
"storybook"

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/preview-api",
"version": "8.5.0-beta.4",
"version": "8.5.0-beta.7",
"description": "",
"keywords": [
"storybook"

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/preview",
"version": "8.5.0-beta.4",
"version": "8.5.0-beta.7",
"description": "",
"keywords": [
"storybook"

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/router",
"version": "8.5.0-beta.4",
"version": "8.5.0-beta.7",
"description": "Core Storybook Router",
"keywords": [
"storybook"

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/telemetry",
"version": "8.5.0-beta.4",
"version": "8.5.0-beta.7",
"description": "Telemetry logging for crash reports and usage statistics",
"keywords": [
"storybook"

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/theming",
"version": "8.5.0-beta.4",
"version": "8.5.0-beta.7",
"description": "Core Storybook Components",
"keywords": [
"storybook"

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/types",
"version": "8.5.0-beta.4",
"version": "8.5.0-beta.7",
"description": "Core Storybook TS Types",
"keywords": [
"storybook"

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/angular",
"version": "8.5.0-beta.4",
"version": "8.5.0-beta.7",
"description": "Storybook for Angular: Develop Angular components in isolation with hot reloading.",
"keywords": [
"storybook",
@ -44,7 +44,6 @@
"@storybook/manager-api": "workspace:*",
"@storybook/preview-api": "workspace:*",
"@storybook/theming": "workspace:*",
"@types/node": "^22.0.0",
"@types/react": "^18.0.37",
"@types/react-dom": "^18.0.11",
"@types/semver": "^7.3.4",
@ -73,6 +72,7 @@
"@angular/platform-browser": "^17.3.0",
"@angular/platform-browser-dynamic": "^17.3.0",
"@types/cross-spawn": "^6.0.2",
"@types/node": "^22.0.0",
"@types/tmp": "^0.2.3",
"cross-spawn": "^7.0.3",
"tmp": "^0.2.1",

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/ember",
"version": "8.5.0-beta.4",
"version": "8.5.0-beta.7",
"description": "Storybook for Ember: Develop Ember Component in isolation with Hot Reloading.",
"homepage": "https://github.com/storybookjs/storybook/tree/next/code/frameworks/ember",
"bugs": {

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/experimental-nextjs-vite",
"version": "8.5.0-beta.4",
"version": "8.5.0-beta.7",
"description": "Storybook for Next.js and Vite",
"keywords": [
"storybook",
@ -103,7 +103,7 @@
"vite-plugin-storybook-nextjs": "^1.1.0"
},
"devDependencies": {
"@types/node": "^18.0.0",
"@types/node": "^22.0.0",
"next": "^15.0.3",
"typescript": "^5.3.2"
},

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/html-vite",
"version": "8.5.0-beta.4",
"version": "8.5.0-beta.7",
"description": "Storybook for HTML and Vite: Develop HTML in isolation with Hot Reloading.",
"keywords": [
"storybook"

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/html-webpack5",
"version": "8.5.0-beta.4",
"version": "8.5.0-beta.7",
"description": "Storybook for HTML: View HTML snippets in isolation with Hot Reloading.",
"keywords": [
"storybook"
@ -50,10 +50,10 @@
"@storybook/builder-webpack5": "workspace:*",
"@storybook/global": "^5.0.0",
"@storybook/html": "workspace:*",
"@storybook/preset-html-webpack": "workspace:*",
"@types/node": "^22.0.0"
"@storybook/preset-html-webpack": "workspace:*"
},
"devDependencies": {
"@types/node": "^22.0.0",
"typescript": "^5.3.2"
},
"peerDependencies": {

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/nextjs",
"version": "8.5.0-beta.4",
"version": "8.5.0-beta.7",
"description": "Storybook for Next.js",
"keywords": [
"storybook",
@ -148,7 +148,6 @@
"@storybook/preset-react-webpack": "workspace:*",
"@storybook/react": "workspace:*",
"@storybook/test": "workspace:*",
"@types/node": "^22.0.0",
"@types/semver": "^7.3.4",
"babel-loader": "^9.1.3",
"css-loader": "^6.7.3",
@ -175,6 +174,7 @@
"@types/babel__plugin-transform-runtime": "^7",
"@types/babel__preset-env": "^7",
"@types/loader-utils": "^2.0.5",
"@types/node": "^22.0.0",
"@types/react-refresh": "^0",
"next": "^15.0.3",
"typescript": "^5.3.2",

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/preact-vite",
"version": "8.5.0-beta.4",
"version": "8.5.0-beta.7",
"description": "Storybook for Preact and Vite: Develop Preact components in isolation with Hot Reloading.",
"keywords": [
"storybook"

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/preact-webpack5",
"version": "8.5.0-beta.4",
"version": "8.5.0-beta.7",
"description": "Storybook for Preact: Develop Preact Component in isolation.",
"keywords": [
"storybook"
@ -49,10 +49,10 @@
"dependencies": {
"@storybook/builder-webpack5": "workspace:*",
"@storybook/preact": "workspace:*",
"@storybook/preset-preact-webpack": "workspace:*",
"@types/node": "^22.0.0"
"@storybook/preset-preact-webpack": "workspace:*"
},
"devDependencies": {
"@types/node": "^22.0.0",
"preact": "^10.5.13",
"typescript": "^5.3.2"
},

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/react-native-web-vite",
"version": "8.5.0-beta.4",
"version": "8.5.0-beta.7",
"description": "Develop react-native components an isolated web environment with hot reloading.",
"keywords": [
"storybook"
@ -53,12 +53,15 @@
"prep": "jiti ../../../scripts/prepare/bundle.ts"
},
"dependencies": {
"@babel/plugin-transform-modules-commonjs": "^7.26.3",
"@babel/preset-react": "^7.26.3",
"@bunchtogether/vite-plugin-flow": "^1.0.2",
"@joshwooding/vite-plugin-react-docgen-typescript": "0.4.2",
"@storybook/builder-vite": "workspace:*",
"@storybook/react": "workspace:*",
"@storybook/react-vite": "workspace:*",
"@vitejs/plugin-react": "^4.3.2",
"vite-plugin-babel": "^1.3.0",
"vite-tsconfig-paths": "^5.1.4"
},
"devDependencies": {

View File

@ -3,6 +3,7 @@ import { viteFinal as reactViteFinal } from '@storybook/react-vite/preset';
import { esbuildFlowPlugin, flowPlugin } from '@bunchtogether/vite-plugin-flow';
import react from '@vitejs/plugin-react';
import type { InlineConfig, PluginOption } from 'vite';
import babel from 'vite-plugin-babel';
import tsconfigPaths from 'vite-tsconfig-paths';
import type { FrameworkOptions, StorybookConfig } from './types';
@ -64,10 +65,12 @@ export function reactNativeWeb(): PluginOption {
export const viteFinal: StorybookConfig['viteFinal'] = async (config, options) => {
const { mergeConfig } = await import('vite');
const { pluginReactOptions = {} } =
const { pluginReactOptions = {}, pluginBabelOptions = {} } =
await options.presets.apply<FrameworkOptions>('frameworkOptions');
const { plugins = [], ...reactConfigWithoutPlugins } = await reactViteFinal(config, options);
const isDevelopment = options.configType !== 'PRODUCTION';
const reactConfig = await reactViteFinal(config, options);
return mergeConfig(reactConfigWithoutPlugins, {
plugins: [
@ -88,6 +91,7 @@ export const viteFinal: StorybookConfig['viteFinal'] = async (config, options) =
],
optimizeDeps: {
esbuildOptions: {
// fix for react native packages shipping with flow types untranspiled
plugins: [esbuildFlowPlugin(new RegExp(/\.(flow|jsx?)$/), (_path: string) => 'jsx')],
},
},

View File

@ -6,9 +6,17 @@ import type {
} from '@storybook/react-vite';
import type { BabelOptions, Options as ReactOptions } from '@vitejs/plugin-react';
import type { BabelPluginOptions } from 'vite-plugin-babel';
export type FrameworkOptions = FrameworkOptionsBase & {
pluginReactOptions?: Omit<ReactOptions, 'babel'> & { babel?: BabelOptions };
pluginBabelOptions?: BabelPluginOptions & {
presetReact?: {
[key: string]: any;
runtime?: 'automatic' | 'classic';
importSource?: string;
};
};
};
type FrameworkName = CompatibleString<'@storybook/react-native-web-vite'>;

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/react-vite",
"version": "8.5.0-beta.4",
"version": "8.5.0-beta.7",
"description": "Storybook for React and Vite: Develop React components in isolation with Hot Reloading.",
"keywords": [
"storybook"

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/react-webpack5",
"version": "8.5.0-beta.4",
"version": "8.5.0-beta.7",
"description": "Storybook for React: Develop React Component in isolation with Hot Reloading.",
"keywords": [
"storybook"
@ -49,7 +49,9 @@
"dependencies": {
"@storybook/builder-webpack5": "workspace:*",
"@storybook/preset-react-webpack": "workspace:*",
"@storybook/react": "workspace:*",
"@storybook/react": "workspace:*"
},
"devDependencies": {
"@types/node": "^22.0.0"
},
"peerDependencies": {

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/server-webpack5",
"version": "8.5.0-beta.4",
"version": "8.5.0-beta.7",
"description": "Storybook for Server: View HTML snippets from a server in isolation with Hot Reloading.",
"keywords": [
"storybook"
@ -49,10 +49,10 @@
"dependencies": {
"@storybook/builder-webpack5": "workspace:*",
"@storybook/preset-server-webpack": "workspace:*",
"@storybook/server": "workspace:*",
"@types/node": "^22.0.0"
"@storybook/server": "workspace:*"
},
"devDependencies": {
"@types/node": "^22.0.0",
"typescript": "^5.3.2"
},
"peerDependencies": {

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/svelte-vite",
"version": "8.5.0-beta.4",
"version": "8.5.0-beta.7",
"description": "Storybook for Svelte and Vite: Develop Svelte components in isolation with Hot Reloading.",
"keywords": [
"storybook"

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/svelte-webpack5",
"version": "8.5.0-beta.4",
"version": "8.5.0-beta.7",
"description": "Storybook for Svelte: Develop Svelte Component in isolation with Hot Reloading.",
"keywords": [
"storybook"

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/sveltekit",
"version": "8.5.0-beta.4",
"version": "8.5.0-beta.7",
"description": "Storybook for SvelteKit",
"keywords": [
"storybook",

Some files were not shown because too many files have changed in this diff Show More