diff --git a/CHANGELOG.prerelease.md b/CHANGELOG.prerelease.md index 217d74b01133..05bbfb9847d1 100644 --- a/CHANGELOG.prerelease.md +++ b/CHANGELOG.prerelease.md @@ -1,3 +1,9 @@ +## 8.5.0-beta.3 + +- Addon A11y: Fix skipped status handling in Testing Module - [#30077](https://github.com/storybookjs/storybook/pull/30077), thanks @valentinpalkovic! +- Core: Float context menu button on top of story titles in sidebar - [#30080](https://github.com/storybookjs/storybook/pull/30080), thanks @ghengeveld! +- Onboarding: Replace `react-confetti` with `@neoconfetti/react` - [#30098](https://github.com/storybookjs/storybook/pull/30098), thanks @ndelangen! + ## 8.5.0-beta.2 - Addon Test: Clear coverage data when starting or watching - [#30072](https://github.com/storybookjs/storybook/pull/30072), thanks @ghengeveld! diff --git a/code/.storybook/preview.tsx b/code/.storybook/preview.tsx index 04210af3f5af..23f95a0c5d5e 100644 --- a/code/.storybook/preview.tsx +++ b/code/.storybook/preview.tsx @@ -358,5 +358,6 @@ export const parameters = { opacity: 0.4, }, }, - tags: ['test', 'vitest', '!a11ytest'], }; + +export const tags = ['test', 'vitest', '!a11ytest']; diff --git a/code/addons/onboarding/package.json b/code/addons/onboarding/package.json index 4964c7a3a4ba..4b17b10810dd 100644 --- a/code/addons/onboarding/package.json +++ b/code/addons/onboarding/package.json @@ -45,12 +45,12 @@ "prep": "jiti ../../../scripts/prepare/addon-bundle.ts" }, "devDependencies": { + "@neoconfetti/react": "^1.0.0", "@radix-ui/react-dialog": "^1.0.5", "@storybook/icons": "^1.2.12", "@storybook/react": "workspace:*", "framer-motion": "^11.0.3", "react": "^18.2.0", - "react-confetti": "^6.1.0", "react-dom": "^18.2.0", "react-joyride": "^2.8.2", "react-use-measure": "^2.1.1", diff --git a/code/addons/onboarding/src/Onboarding.tsx b/code/addons/onboarding/src/Onboarding.tsx index b861d918204a..54e264f67254 100644 --- a/code/addons/onboarding/src/Onboarding.tsx +++ b/code/addons/onboarding/src/Onboarding.tsx @@ -268,17 +268,7 @@ export default function Onboarding({ api }: { api: API }) { return ( - {showConfetti && ( - { - confetti?.reset(); - setShowConfetti(false); - }} - /> - )} + {showConfetti && } {step === '1:Intro' ? ( setStep('2:Controls')} /> ) : ( diff --git a/code/addons/onboarding/src/components/Confetti/Confetti.stories.tsx b/code/addons/onboarding/src/components/Confetti/Confetti.stories.tsx index b55fdf783b34..3540aadc2a85 100644 --- a/code/addons/onboarding/src/components/Confetti/Confetti.stories.tsx +++ b/code/addons/onboarding/src/components/Confetti/Confetti.stories.tsx @@ -8,11 +8,19 @@ const meta: Meta = { component: Confetti, parameters: { chromatic: { disableSnapshot: true }, + layout: 'fullscreen', }, decorators: [ (StoryFn) => ( -
- +
+ Falling confetti! 🎉
), @@ -23,41 +31,4 @@ export default meta; type Story = StoryObj; -export const Default: Story = { - args: { - recycle: true, - numberOfPieces: 200, - top: undefined, - left: undefined, - width: undefined, - height: undefined, - friction: 0.99, - wind: 0, - gravity: 0.1, - initialVelocityX: 4, - initialVelocityY: 10, - tweenDuration: 5000, - }, -}; - -export const OneTimeConfetti: Story = { - args: { - ...Default.args, - numberOfPieces: 800, - recycle: false, - tweenDuration: 20000, - onConfettiComplete: (confetti) => { - confetti?.reset(); - }, - }, -}; - -export const Positioned: Story = { - args: { - ...Default.args, - top: 100, - left: 300, - width: 300, - height: 250, - }, -}; +export const Default: Story = {}; diff --git a/code/addons/onboarding/src/components/Confetti/Confetti.tsx b/code/addons/onboarding/src/components/Confetti/Confetti.tsx index b9b816d3e051..cebc40454909 100644 --- a/code/addons/onboarding/src/components/Confetti/Confetti.tsx +++ b/code/addons/onboarding/src/components/Confetti/Confetti.tsx @@ -1,131 +1,34 @@ -import React, { useEffect } from 'react'; -import { useState } from 'react'; -import { createPortal } from 'react-dom'; +import React, { type ComponentProps } from 'react'; import { styled } from 'storybook/internal/theming'; -import ReactConfetti from 'react-confetti'; +import { Confetti as ReactConfetti } from '@neoconfetti/react'; -interface ConfettiProps extends Omit, 'drawShape'> { - top?: number; - left?: number; - width?: number; - height?: number; - numberOfPieces?: number; - recycle?: boolean; - colors?: string[]; -} +const Wrapper = styled.div({ + zIndex: 9999, + position: 'fixed', + top: 0, + left: '50%', + width: '50%', + height: '100%', +}); -const Wrapper = styled.div<{ - width: number; - height: number; - top: number; - left: number; -}>(({ width, height, left, top }) => ({ - width: `${width}px`, - height: `${height}px`, - left: `${left}px`, - top: `${top}px`, - position: 'relative', - overflow: 'hidden', -})); - -export function Confetti({ - top = 0, - left = 0, - width = window.innerWidth, - height = window.innerHeight, +export const Confetti = React.memo(function Confetti({ + timeToFade = 5000, colors = ['#CA90FF', '#FC521F', '#66BF3C', '#FF4785', '#FFAE00', '#1EA7FD'], ...confettiProps -}: ConfettiProps): React.ReactPortal { - const [confettiContainer] = useState(() => { - const container = document.createElement('div'); - container.setAttribute('id', 'confetti-container'); - container.setAttribute( - 'style', - 'position: fixed; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; z-index: 9999;' - ); - - return container; - }); - - useEffect(() => { - document.body.appendChild(confettiContainer); - - return () => { - document.body.removeChild(confettiContainer); - }; - }, []); - - return createPortal( - - - , - confettiContainer +}: ComponentProps & { timeToFade?: number }) { + return ( + + + ); -} - -enum ParticleShape { - Circle = 1, - Square = 2, - TShape = 3, - LShape = 4, - Triangle = 5, - QuarterCircle = 6, -} - -function getRandomInt(min: number, max: number) { - return Math.floor(Math.random() * (max - min)) + min; -} - -function draw(this: any, context: CanvasRenderingContext2D) { - this.shape = this.shape || getRandomInt(1, 6); - - switch (this.shape) { - case ParticleShape.Square: { - const cornerRadius = 2; - const width = this.w / 2; - const height = this.h / 2; - - context.moveTo(-width + cornerRadius, -height); - context.lineTo(width - cornerRadius, -height); - context.arcTo(width, -height, width, -height + cornerRadius, cornerRadius); - context.lineTo(width, height - cornerRadius); - context.arcTo(width, height, width - cornerRadius, height, cornerRadius); - context.lineTo(-width + cornerRadius, height); - context.arcTo(-width, height, -width, height - cornerRadius, cornerRadius); - context.lineTo(-width, -height + cornerRadius); - context.arcTo(-width, -height, -width + cornerRadius, -height, cornerRadius); - - break; - } - case ParticleShape.TShape: { - context.rect(-4, -4, 8, 16); - context.rect(-12, -4, 24, 8); - break; - } - case ParticleShape.LShape: { - context.rect(-4, -4, 8, 16); - context.rect(-4, -4, 24, 8); - break; - } - case ParticleShape.Circle: { - context.arc(0, 0, this.radius, 0, 2 * Math.PI); - break; - } - case ParticleShape.Triangle: { - context.moveTo(16, 4); - context.lineTo(4, 24); - context.lineTo(24, 24); - break; - } - case ParticleShape.QuarterCircle: { - context.arc(4, -4, 4, -Math.PI / 2, 0); - context.lineTo(4, 0); - break; - } - } - - context.closePath(); - context.fill(); -} +}); diff --git a/code/addons/test/src/components/TestProviderRender.stories.tsx b/code/addons/test/src/components/TestProviderRender.stories.tsx index dead913949cf..1189248f3d53 100644 --- a/code/addons/test/src/components/TestProviderRender.stories.tsx +++ b/code/addons/test/src/components/TestProviderRender.stories.tsx @@ -52,6 +52,10 @@ const baseState: TestProviderState = { coverage: false, }, details: { + config: { + a11y: false, + coverage: false, + }, testResults: [ { endTime: 0, @@ -141,6 +145,10 @@ export const WithCoverageNegative: Story = { ...config, ...baseState, details: { + config: { + a11y: false, + coverage: true, + }, testResults: [], coverageSummary: { percentage: 20, @@ -162,6 +170,10 @@ export const WithCoverageWarning: Story = { ...baseState, details: { testResults: [], + config: { + a11y: false, + coverage: true, + }, coverageSummary: { percentage: 50, status: 'warning', @@ -182,6 +194,10 @@ export const WithCoveragePositive: Story = { ...baseState, details: { testResults: [], + config: { + a11y: false, + coverage: true, + }, coverageSummary: { percentage: 80, status: 'positive', @@ -206,6 +222,10 @@ export const Editing: Story = { }, details: { testResults: [], + config: { + a11y: false, + coverage: false, + }, }, }, }, @@ -229,6 +249,10 @@ export const EditingAndWatching: Story = { }, details: { testResults: [], + config: { + a11y: true, + coverage: true, // should be automatically disabled in the UI + }, }, }, }, diff --git a/code/addons/test/src/components/TestProviderRender.tsx b/code/addons/test/src/components/TestProviderRender.tsx index e04772447c43..29b0f950da8c 100644 --- a/code/addons/test/src/components/TestProviderRender.tsx +++ b/code/addons/test/src/components/TestProviderRender.tsx @@ -138,12 +138,14 @@ export const TestProviderRender: FC< return 'unknown'; } - if (!a11yResults) { + const definedA11yResults = a11yResults?.filter(Boolean) ?? []; + + if (!definedA11yResults || definedA11yResults.length === 0) { return 'unknown'; } - const failed = a11yResults.some((result) => result?.status === 'failed'); - const warning = a11yResults.some((result) => result?.status === 'warning'); + const failed = definedA11yResults.some((result) => result?.status === 'failed'); + const warning = definedA11yResults.some((result) => result?.status === 'warning'); if (failed) { return 'negative'; @@ -154,11 +156,24 @@ export const TestProviderRender: FC< return 'positive'; }, [state.running, isA11yAddon, config.a11y, a11yResults]); - const a11yNotPassedAmount = a11yResults?.filter( - (result) => result?.status === 'failed' || result?.status === 'warning' - ).length; + const a11yNotPassedAmount = state.config?.a11y + ? a11yResults?.filter((result) => result?.status === 'failed' || result?.status === 'warning') + .length + : undefined; + + const a11ySkippedAmount = + state.running || !state?.details.config?.a11y || !state.config.a11y + ? null + : a11yResults?.filter((result) => !result).length; + + const a11ySkippedLabel = a11ySkippedAmount + ? a11ySkippedAmount === 1 && isStoryEntry + ? '(skipped)' + : `(${a11ySkippedAmount} skipped)` + : ''; const storyId = isStoryEntry ? entryId : undefined; + const results = (state.details?.testResults || []) .flatMap((test) => { if (!entryId) { @@ -333,7 +348,7 @@ export const TestProviderRender: FC< )} {isA11yAddon && ( Accessibility} + title={Accessibility {a11ySkippedLabel}} onClick={ (a11yStatus === 'negative' || a11yStatus === 'warning') && a11yResults.length ? () => { diff --git a/code/addons/test/src/constants.ts b/code/addons/test/src/constants.ts index 0453930e3758..d57ff1b1da8a 100644 --- a/code/addons/test/src/constants.ts +++ b/code/addons/test/src/constants.ts @@ -19,6 +19,7 @@ export interface Config { export type Details = { testResults: TestResult[]; + config: Config; coverageSummary?: { status: 'positive' | 'warning' | 'negative' | 'unknown'; percentage: number; diff --git a/code/addons/test/src/node/reporter.ts b/code/addons/test/src/node/reporter.ts index 43191cbc2fdc..ecda9ab4a672 100644 --- a/code/addons/test/src/node/reporter.ts +++ b/code/addons/test/src/node/reporter.ts @@ -173,6 +173,7 @@ export class StorybookReporter implements Reporter { } as TestingModuleProgressReportProgress, details: { testResults, + config: this.testManager.config, }, }; } diff --git a/code/addons/test/src/node/test-manager.test.ts b/code/addons/test/src/node/test-manager.test.ts index db77ab2f3e2e..7056aee5666b 100644 --- a/code/addons/test/src/node/test-manager.test.ts +++ b/code/addons/test/src/node/test-manager.test.ts @@ -105,11 +105,11 @@ describe('TestManager', () => { it('should handle watch mode request', async () => { const testManager = await TestManager.start(mockChannel, options); - expect(testManager.watchMode).toBe(false); + expect(testManager.config.watchMode).toBe(false); expect(createVitest).toHaveBeenCalledTimes(1); await testManager.handleWatchModeRequest({ providerId: TEST_PROVIDER_ID, watchMode: true }); - expect(testManager.watchMode).toBe(true); + expect(testManager.config.watchMode).toBe(true); expect(createVitest).toHaveBeenCalledTimes(1); // shouldn't restart vitest }); @@ -149,7 +149,7 @@ describe('TestManager', () => { it('should handle coverage toggling', async () => { const testManager = await TestManager.start(mockChannel, options); - expect(testManager.coverage).toBe(false); + expect(testManager.config.coverage).toBe(false); expect(createVitest).toHaveBeenCalledTimes(1); createVitest.mockClear(); @@ -157,7 +157,7 @@ describe('TestManager', () => { providerId: TEST_PROVIDER_ID, config: { coverage: true, a11y: false }, }); - expect(testManager.coverage).toBe(true); + expect(testManager.config.coverage).toBe(true); expect(createVitest).toHaveBeenCalledTimes(1); createVitest.mockClear(); @@ -165,21 +165,21 @@ describe('TestManager', () => { providerId: TEST_PROVIDER_ID, config: { coverage: false, a11y: false }, }); - expect(testManager.coverage).toBe(false); + expect(testManager.config.coverage).toBe(false); expect(createVitest).toHaveBeenCalledTimes(1); }); it('should temporarily disable coverage on focused tests', async () => { vitest.globTestSpecs.mockImplementation(() => tests); const testManager = await TestManager.start(mockChannel, options); - expect(testManager.coverage).toBe(false); + expect(testManager.config.coverage).toBe(false); expect(createVitest).toHaveBeenCalledTimes(1); await testManager.handleConfigChange({ providerId: TEST_PROVIDER_ID, config: { coverage: true, a11y: false }, }); - expect(testManager.coverage).toBe(true); + expect(testManager.config.coverage).toBe(true); expect(createVitest).toHaveBeenCalledTimes(2); await testManager.handleRunRequest({ diff --git a/code/addons/test/src/node/test-manager.ts b/code/addons/test/src/node/test-manager.ts index 1595c2da3fa9..1a19e587eeea 100644 --- a/code/addons/test/src/node/test-manager.ts +++ b/code/addons/test/src/node/test-manager.ts @@ -18,9 +18,11 @@ import { VitestManager } from './vitest-manager'; export class TestManager { vitestManager: VitestManager; - watchMode = false; - - coverage = false; + config = { + watchMode: false, + coverage: false, + a11y: false, + }; constructor( private channel: Channel, @@ -44,13 +46,19 @@ export class TestManager { return; } + const previousConfig = this.config; + + this.config = { + ...this.config, + ...payload.config, + } satisfies Config; + process.env.VITEST_STORYBOOK_CONFIG = JSON.stringify(payload.config); - if (this.coverage !== payload.config.coverage) { - this.coverage = payload.config.coverage; + if (previousConfig.coverage !== payload.config.coverage) { try { await this.vitestManager.restartVitest({ - coverage: this.coverage, + coverage: this.config.coverage, }); } catch (e) { this.reportFatalError('Failed to change coverage configuration', e); @@ -62,7 +70,7 @@ export class TestManager { if (payload.providerId !== TEST_PROVIDER_ID) { return; } - this.watchMode = payload.watchMode; + this.config.watchMode = payload.watchMode; if (payload.config) { this.handleConfigChange({ @@ -71,14 +79,14 @@ export class TestManager { }); } - if (this.coverage) { + if (this.config.coverage) { try { if (payload.watchMode) { // if watch mode is toggled on and coverage is already enabled, restart vitest without coverage to automatically disable it await this.vitestManager.restartVitest({ coverage: false }); } else { // if watch mode is toggled off and coverage is already enabled, restart vitest with coverage to automatically re-enable it - await this.vitestManager.restartVitest({ coverage: this.coverage }); + await this.vitestManager.restartVitest({ coverage: this.config.coverage }); } } catch (e) { this.reportFatalError('Failed to change watch mode while coverage was enabled', e); @@ -104,7 +112,7 @@ export class TestManager { as a coverage report for a subset of stories is not useful. */ const temporarilyDisableCoverage = - this.coverage && !this.watchMode && (payload.storyIds ?? []).length > 0; + this.config.coverage && !this.config.watchMode && (payload.storyIds ?? []).length > 0; if (temporarilyDisableCoverage) { await this.vitestManager.restartVitest({ coverage: false, @@ -117,7 +125,7 @@ export class TestManager { if (temporarilyDisableCoverage) { // Re-enable coverage if it was temporarily disabled because of a subset of stories was run - await this.vitestManager.restartVitest({ coverage: this.coverage }); + await this.vitestManager.restartVitest({ coverage: this.config.coverage }); } } catch (e) { this.reportFatalError('Failed to run tests', e); diff --git a/code/addons/test/src/node/vitest-manager.ts b/code/addons/test/src/node/vitest-manager.ts index 26dbe16ab526..0a5ff8ab0b4e 100644 --- a/code/addons/test/src/node/vitest-manager.ts +++ b/code/addons/test/src/node/vitest-manager.ts @@ -199,7 +199,7 @@ export class VitestManager { this.filterStories(story, spec.moduleId, { include, exclude, skip }) ); if (matches.length) { - if (!this.testManager.watchMode) { + if (!this.testManager.config.watchMode) { // Clear the file cache if watch mode is not enabled this.updateLastChanged(spec.moduleId); } @@ -320,7 +320,7 @@ export class VitestManager { // 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.watchMode) { + if (!this.testManager.config.watchMode) { return; } await this.runAffectedTests(file); diff --git a/code/core/assets/server/addon.tsconfig.json b/code/core/assets/server/addon.tsconfig.json index 97efabb3d68b..38452bcdfa20 100644 --- a/code/core/assets/server/addon.tsconfig.json +++ b/code/core/assets/server/addon.tsconfig.json @@ -3,4 +3,4 @@ "jsx": "react", "jsxImportSource": "react" } -} \ No newline at end of file +} diff --git a/code/core/src/manager/components/sidebar/ContextMenu.tsx b/code/core/src/manager/components/sidebar/ContextMenu.tsx index a2d98eab6a85..ac4aeb671d55 100644 --- a/code/core/src/manager/components/sidebar/ContextMenu.tsx +++ b/code/core/src/manager/components/sidebar/ContextMenu.tsx @@ -2,6 +2,7 @@ import type { ComponentProps, FC, SyntheticEvent } from 'react'; import React, { useMemo, useState } from 'react'; import { TooltipLinkList, WithTooltip } from '@storybook/core/components'; +import { styled } from '@storybook/core/theming'; import { type API_HashEntry, Addon_TypesEnum } from '@storybook/core/types'; import { EllipsisIcon } from '@storybook/icons'; @@ -18,6 +19,16 @@ const empty = { node: null, }; +const PositionedWithTooltip = styled(WithTooltip)({ + position: 'absolute', + right: 0, +}); + +const FloatingStatusButton = styled(StatusButton)({ + background: 'var(--tree-node-background-hover)', + boxShadow: '0 0 5px 5px var(--tree-node-background-hover)', +}); + export const useContextMenu = (context: API_HashEntry, links: Link[], api: API) => { const [hoverCount, setHoverCount] = useState(0); const [isOpen, setIsOpen] = useState(false); @@ -63,7 +74,7 @@ export const useContextMenu = (context: API_HashEntry, links: Link[], api: API) return { onMouseEnter: handlers.onMouseEnter, node: isRendered ? ( - } > - + - - + + ) : null, }; }, [context, handlers, isOpen, isRendered, links]); diff --git a/code/core/src/manager/components/sidebar/StatusButton.tsx b/code/core/src/manager/components/sidebar/StatusButton.tsx index 9d1b49998df7..8002cc87ba33 100644 --- a/code/core/src/manager/components/sidebar/StatusButton.tsx +++ b/code/core/src/manager/components/sidebar/StatusButton.tsx @@ -3,7 +3,7 @@ import { styled } from '@storybook/core/theming'; import type { API_StatusValue } from '@storybook/types'; import type { Theme } from '@emotion/react'; -import { transparentize } from 'polished'; +import { darken, lighten, transparentize } from 'polished'; const withStatusColor = ({ theme, status }: { theme: Theme; status: API_StatusValue }) => { const defaultColor = @@ -43,6 +43,19 @@ export const StatusButton = styled(IconButton)<{ '&:hover': { color: theme.color.secondary, + background: + theme.base === 'dark' + ? darken(0.3, theme.color.secondary) + : lighten(0.4, theme.color.secondary), + }, + + '[data-selected="true"] &': { + background: theme.color.secondary, + boxShadow: `0 0 5px 5px ${theme.color.secondary}`, + + '&:hover': { + background: lighten(0.1, theme.color.secondary), + }, }, '&:focus': { diff --git a/code/core/src/manager/components/sidebar/Tree.tsx b/code/core/src/manager/components/sidebar/Tree.tsx index d08ab8ffc4d7..cbfc1e330be7 100644 --- a/code/core/src/manager/components/sidebar/Tree.tsx +++ b/code/core/src/manager/components/sidebar/Tree.tsx @@ -24,9 +24,10 @@ import type { StoryEntry, } from '@storybook/core/manager-api'; -import { transparentize } from 'polished'; +import { darken, lighten } from 'polished'; import type { Link } from '../../../components/components/tooltip/TooltipLinkList'; +import { MEDIA_DESKTOP_BREAKPOINT } from '../../constants'; import { getGroupStatus, getHighestStatus, statusMapping } from '../../utils/status'; import { createId, @@ -66,7 +67,7 @@ const CollapseButton = styled.button(({ theme }) => ({ '&:hover, &:focus': { outline: 'none', - background: transparentize(0.93, theme.color.secondary), + background: 'var(--tree-node-background-hover)', }, })); @@ -79,9 +80,19 @@ export const LeafNodeStyleWrapper = styled.div(({ theme }) => ({ background: 'transparent', minHeight: 28, borderRadius: 4, + overflow: 'hidden', + '--tree-node-background-hover': theme.background.content, + + [MEDIA_DESKTOP_BREAKPOINT]: { + '--tree-node-background-hover': theme.background.app, + }, '&:hover, &:focus': { - background: transparentize(0.93, theme.color.secondary), + '--tree-node-background-hover': + theme.base === 'dark' + ? darken(0.35, theme.color.secondary) + : lighten(0.45, theme.color.secondary), + background: 'var(--tree-node-background-hover)', outline: 'none', }, @@ -94,11 +105,11 @@ export const LeafNodeStyleWrapper = styled.div(({ theme }) => ({ }, '& [data-displayed="on"] + *': { - display: 'none', + visibility: 'hidden', }, '&:hover [data-displayed="off"] + *': { - display: 'none', + visibility: 'hidden', }, '&[data-selected="true"]': { @@ -107,7 +118,8 @@ export const LeafNodeStyleWrapper = styled.div(({ theme }) => ({ fontWeight: theme.typography.weight.bold, '&&:hover, &&:focus': { - background: theme.color.secondary, + '--tree-node-background-hover': theme.color.secondary, + background: 'var(--tree-node-background-hover)', }, svg: { color: theme.color.lightest }, }, diff --git a/code/e2e-tests/addon-onboarding.spec.ts b/code/e2e-tests/addon-onboarding.spec.ts new file mode 100644 index 000000000000..85181d8abf2c --- /dev/null +++ b/code/e2e-tests/addon-onboarding.spec.ts @@ -0,0 +1,53 @@ +import { expect, test } from '@playwright/test'; +import process from 'process'; + +import { SbPage } from './util'; + +const storybookUrl = process.env.STORYBOOK_URL || 'http://localhost:8001'; +const templateName = process.env.STORYBOOK_TEMPLATE_NAME || ''; +const type = process.env.STORYBOOK_TYPE || 'dev'; + +const supportsOnboarding = + templateName.includes('react') || + templateName.includes('vue3') || + templateName.includes('angular') || + templateName.includes('next'); + +test.describe('addon-onboarding', () => { + test.skip(type === 'build', `Skipping addon tests for production Storybooks`); + test.skip( + !supportsOnboarding, + `Skipping ${templateName}, which does not have addon-onboarding set up.` + ); + test('the onboarding flow', async ({ page }) => { + await page.goto(`${storybookUrl}/?path=/onboarding`); + const sbPage = new SbPage(page, expect); + await sbPage.waitUntilLoaded(); + + await expect(page.getByRole('heading', { name: 'Meet your new frontend' })).toBeVisible(); + await page.locator('#storybook-addon-onboarding').getByRole('button').click(); + + await expect(page.getByText('Interactive story playground')).toBeVisible(); + await page.getByLabel('Next').click(); + + await expect(page.getByText('Save your changes as a new')).toBeVisible(); + await page.getByLabel('Next').click(); + + await expect(page.getByRole('heading', { name: 'Create new story' })).toBeVisible(); + await page.getByPlaceholder('Story export name').click(); + + // this is needed because the e2e test will generate a new file in the system + // which we don't know of its location (it runs in different sandboxes) + // so we just create a random id to make it easier to run tests + const id = Math.random().toString(36).substring(7); + await page.getByPlaceholder('Story export name').fill('Test-' + id); + await page.getByRole('button', { name: 'Create' }).click(); + + await expect(page.getByText('You just added your first')).toBeVisible(); + await page.getByLabel('Last').click(); + + await expect( + sbPage.previewIframe().getByRole('heading', { name: 'Configure your project' }) + ).toBeVisible(); + }); +}); diff --git a/code/frameworks/angular/src/builders/build-storybook/schema.json b/code/frameworks/angular/src/builders/build-storybook/schema.json index 9393f53d49b7..77e455cdb1be 100644 --- a/code/frameworks/angular/src/builders/build-storybook/schema.json +++ b/code/frameworks/angular/src/builders/build-storybook/schema.json @@ -67,27 +67,18 @@ "compodocArgs": { "type": "array", "description": "Compodoc options : https://compodoc.app/guides/options.html. Options `-p` with tsconfig path and `-d` with workspace root is always given.", - "default": [ - "-e", - "json" - ], + "default": ["-e", "json"], "items": { "type": "string" } }, "webpackStatsJson": { - "type": [ - "boolean", - "string" - ], + "type": ["boolean", "string"], "description": "Write Webpack Stats JSON to disk", "default": false }, "statsJson": { - "type": [ - "boolean", - "string" - ], + "type": ["boolean", "string"], "description": "Write stats JSON to disk", "default": false }, @@ -127,10 +118,7 @@ } }, "sourceMap": { - "type": [ - "boolean", - "object" - ], + "type": ["boolean", "object"], "description": "Configure sourcemaps. See: https://angular.io/guide/workspace-config#source-map-configuration", "default": false } @@ -168,11 +156,7 @@ } }, "additionalProperties": false, - "required": [ - "glob", - "input", - "output" - ] + "required": ["glob", "input", "output"] }, { "type": "string" @@ -200,9 +184,7 @@ } }, "additionalProperties": false, - "required": [ - "input" - ] + "required": ["input"] }, { "type": "string", @@ -211,4 +193,4 @@ ] } } -} \ No newline at end of file +} diff --git a/code/frameworks/angular/src/builders/start-storybook/schema.json b/code/frameworks/angular/src/builders/start-storybook/schema.json index da3b697867fd..64d53bd5481e 100644 --- a/code/frameworks/angular/src/builders/start-storybook/schema.json +++ b/code/frameworks/angular/src/builders/start-storybook/schema.json @@ -94,10 +94,7 @@ "compodocArgs": { "type": "array", "description": "Compodoc options : https://compodoc.app/guides/options.html. Options `-p` with tsconfig path and `-d` with workspace root is always given.", - "default": [ - "-e", - "json" - ], + "default": ["-e", "json"], "items": { "type": "string" } @@ -138,18 +135,12 @@ "description": "URL path to be appended when visiting Storybook for the first time" }, "webpackStatsJson": { - "type": [ - "boolean", - "string" - ], + "type": ["boolean", "string"], "description": "Write Webpack Stats JSON to disk", "default": false }, "statsJson": { - "type": [ - "boolean", - "string" - ], + "type": ["boolean", "string"], "description": "Write stats JSON to disk", "default": false }, @@ -163,10 +154,7 @@ "pattern": "(silly|verbose|info|warn|silent)" }, "sourceMap": { - "type": [ - "boolean", - "object" - ], + "type": ["boolean", "object"], "description": "Configure sourcemaps. See: https://angular.io/guide/workspace-config#source-map-configuration", "default": false } @@ -204,11 +192,7 @@ } }, "additionalProperties": false, - "required": [ - "glob", - "input", - "output" - ] + "required": ["glob", "input", "output"] }, { "type": "string" @@ -236,9 +220,7 @@ } }, "additionalProperties": false, - "required": [ - "input" - ] + "required": ["input"] }, { "type": "string", @@ -247,4 +229,4 @@ ] } } -} \ No newline at end of file +} diff --git a/code/frameworks/react-vite/package.json b/code/frameworks/react-vite/package.json index a59dd55dd0d2..a1edb0aa9bc6 100644 --- a/code/frameworks/react-vite/package.json +++ b/code/frameworks/react-vite/package.json @@ -32,6 +32,9 @@ }, "./package.json": "./package.json" }, + "main": "dist/index.js", + "module": "dist/index.mjs", + "types": "dist/index.d.ts", "typesVersions": { "*": { "*": [ @@ -42,9 +45,6 @@ ] } }, - "main": "dist/index.js", - "module": "dist/index.mjs", - "types": "dist/index.d.ts", "files": [ "dist/**/*", "README.md", diff --git a/code/package.json b/code/package.json index 7b9fd27a6318..eac341475722 100644 --- a/code/package.json +++ b/code/package.json @@ -294,5 +294,6 @@ "Dependency Upgrades" ] ] - } + }, + "deferredNextVersion": "8.5.0-beta.3" } diff --git a/code/yarn.lock b/code/yarn.lock index 2e83b8e16192..13e798756fd5 100644 --- a/code/yarn.lock +++ b/code/yarn.lock @@ -4118,6 +4118,13 @@ __metadata: languageName: node linkType: hard +"@neoconfetti/react@npm:^1.0.0": + version: 1.0.0 + resolution: "@neoconfetti/react@npm:1.0.0" + checksum: 10c0/dfa487965b69f88b39562ccd910114cd68b00a90c7eb79cfb1a483c7ac717b720f9f095e5aea13cef8a9b9bea05533d380ddff5e44d3bc3f7dc4d5c66716765c + languageName: node + linkType: hard + "@next/env@npm:15.0.3, @next/env@npm:^15.0.3": version: 15.0.3 resolution: "@next/env@npm:15.0.3" @@ -5749,12 +5756,12 @@ __metadata: version: 0.0.0-use.local resolution: "@storybook/addon-onboarding@workspace:addons/onboarding" dependencies: + "@neoconfetti/react": "npm:^1.0.0" "@radix-ui/react-dialog": "npm:^1.0.5" "@storybook/icons": "npm:^1.2.12" "@storybook/react": "workspace:*" framer-motion: "npm:^11.0.3" react: "npm:^18.2.0" - react-confetti: "npm:^6.1.0" react-dom: "npm:^18.2.0" react-joyride: "npm:^2.8.2" react-use-measure: "npm:^2.1.1" diff --git a/docs/versions/next.json b/docs/versions/next.json index 7399d21ebb8d..35963144623a 100644 --- a/docs/versions/next.json +++ b/docs/versions/next.json @@ -1 +1 @@ -{"version":"8.5.0-beta.2","info":{"plain":"- Addon Test: Clear coverage data when starting or watching - [#30072](https://github.com/storybookjs/storybook/pull/30072), thanks @ghengeveld!\n- Addon Test: Improve error message on missing coverage package - [#30088](https://github.com/storybookjs/storybook/pull/30088), thanks @JReinhold!\n- UI: Fix test provider event handling on startup - [#30083](https://github.com/storybookjs/storybook/pull/30083), thanks @ghengeveld!\n- UI: Keep failing stories in the sidebar, disregarding filters - [#30086](https://github.com/storybookjs/storybook/pull/30086), thanks @JReinhold!"}} +{"version":"8.5.0-beta.3","info":{"plain":"- Addon A11y: Fix skipped status handling in Testing Module - [#30077](https://github.com/storybookjs/storybook/pull/30077), thanks @valentinpalkovic!\n- Core: Float context menu button on top of story titles in sidebar - [#30080](https://github.com/storybookjs/storybook/pull/30080), thanks @ghengeveld!\n- Onboarding: Replace `react-confetti` with `@neoconfetti/react` - [#30098](https://github.com/storybookjs/storybook/pull/30098), thanks @ndelangen!"}}