From 6603753e3d74a1f4d77bf5e6382e2a2652ada58d Mon Sep 17 00:00:00 2001 From: Norbert de Langen Date: Wed, 18 Dec 2024 10:02:21 +0100 Subject: [PATCH 1/7] use react-confetti source to prevent https://github.com/storybookjs/storybook/issues/30095 --- code/addons/onboarding/src/components/Confetti/Confetti.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/addons/onboarding/src/components/Confetti/Confetti.tsx b/code/addons/onboarding/src/components/Confetti/Confetti.tsx index b9b816d3e051..4041ab7b89a5 100644 --- a/code/addons/onboarding/src/components/Confetti/Confetti.tsx +++ b/code/addons/onboarding/src/components/Confetti/Confetti.tsx @@ -4,7 +4,7 @@ import { createPortal } from 'react-dom'; import { styled } from 'storybook/internal/theming'; -import ReactConfetti from 'react-confetti'; +import { ReactConfetti } from 'react-confetti/src/ReactConfetti'; interface ConfettiProps extends Omit, 'drawShape'> { top?: number; From cc2a84a7552dc8fc8600f744a6583f1a3f0bcebd Mon Sep 17 00:00:00 2001 From: Norbert de Langen Date: Wed, 18 Dec 2024 10:27:58 +0100 Subject: [PATCH 2/7] Add E2E tests --- code/e2e-tests/addon-onboarding.spec.ts | 51 +++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 code/e2e-tests/addon-onboarding.spec.ts diff --git a/code/e2e-tests/addon-onboarding.spec.ts b/code/e2e-tests/addon-onboarding.spec.ts new file mode 100644 index 000000000000..64e10e269d9d --- /dev/null +++ b/code/e2e-tests/addon-onboarding.spec.ts @@ -0,0 +1,51 @@ +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 supportsOnboarding = + templateName.includes('react') || + templateName.includes('vue3') || + templateName.includes('angular') || + templateName.includes('next'); + +test.describe('addon-onboarding', () => { + test.skip( + !supportsOnboarding, + `Skipping ${templateName}, which does not have addon-onboarding set up.` + ); + test('should load the onboarding screen', 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(); + }); +}); From 9a0f2fd086259aa151b8338d014313a3d6ccec8e Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Wed, 18 Dec 2024 11:01:03 +0100 Subject: [PATCH 3/7] Addon Onboarding: Replace react-confetti with react-confetti-boom --- code/addons/onboarding/package.json | 4 +- code/addons/onboarding/src/Onboarding.tsx | 12 +- .../components/Confetti/Confetti.stories.tsx | 51 ++----- .../src/components/Confetti/Confetti.tsx | 138 +++--------------- code/yarn.lock | 12 +- 5 files changed, 44 insertions(+), 173 deletions(-) diff --git a/code/addons/onboarding/package.json b/code/addons/onboarding/package.json index 2ae0859fb9d3..ce9dbd9ee074 100644 --- a/code/addons/onboarding/package.json +++ b/code/addons/onboarding/package.json @@ -44,15 +44,13 @@ "check": "jiti ../../../scripts/prepare/check.ts", "prep": "jiti ../../../scripts/prepare/addon-bundle.ts" }, - "dependencies": { - "react-confetti": "^6.1.0" - }, "devDependencies": { "@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-boom": "^1.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 4041ab7b89a5..e5e04628193d 100644 --- a/code/addons/onboarding/src/components/Confetti/Confetti.tsx +++ b/code/addons/onboarding/src/components/Confetti/Confetti.tsx @@ -1,131 +1,33 @@ -import React, { useEffect } from 'react'; +import React, { type ComponentProps, useEffect } from 'react'; import { useState } from 'react'; -import { createPortal } from 'react-dom'; -import { styled } from 'storybook/internal/theming'; - -import { ReactConfetti } from 'react-confetti/src/ReactConfetti'; - -interface ConfettiProps extends Omit, 'drawShape'> { - top?: number; - left?: number; - width?: number; - height?: number; - numberOfPieces?: number; - recycle?: boolean; - colors?: string[]; -} - -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', -})); +import ReactConfetti from 'react-confetti-boom'; export function Confetti({ - top = 0, - left = 0, - width = window.innerWidth, - height = window.innerHeight, + 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; - }); +}: ComponentProps & { timeToFade?: number }) { + const [particleCount, setParticleCount] = useState(42); useEffect(() => { - document.body.appendChild(confettiContainer); + const timeout = setTimeout(() => { + setParticleCount(0); + }, timeToFade); return () => { - document.body.removeChild(confettiContainer); + clearTimeout(timeout); }; - }, []); - - return createPortal( - - - , - confettiContainer + }, [timeToFade]); + + 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/yarn.lock b/code/yarn.lock index f0862ce5333e..5f206a86fd7a 100644 --- a/code/yarn.lock +++ b/code/yarn.lock @@ -5742,7 +5742,7 @@ __metadata: "@storybook/react": "workspace:*" framer-motion: "npm:^11.0.3" react: "npm:^18.2.0" - react-confetti: "npm:^6.1.0" + react-confetti-boom: "npm:^1.1.0" react-dom: "npm:^18.2.0" react-joyride: "npm:^2.8.2" react-use-measure: "npm:^2.1.1" @@ -24167,6 +24167,16 @@ __metadata: languageName: node linkType: hard +"react-confetti-boom@npm:^1.1.0": + version: 1.1.0 + resolution: "react-confetti-boom@npm:1.1.0" + peerDependencies: + react: ">=16.8.0" + react-dom: ">=16.8.0" + checksum: 10c0/76faf3e5cc3284b7dc0297ab7189ff92bbf74379a9478096bc8c8d7b16a82badea74c2e57f7313cd2a55ccf3c8dc50d51165fbf00c9fee5b9ed241369812d686 + languageName: node + linkType: hard + "react-confetti@npm:^6.1.0": version: 6.1.0 resolution: "react-confetti@npm:6.1.0" From 792ac4c3b2627a1c327e49796015beaf4d25e6e1 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Wed, 18 Dec 2024 11:12:23 +0100 Subject: [PATCH 4/7] Add a wrapper --- .../src/components/Confetti/Confetti.tsx | 29 ++++++++++++++----- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/code/addons/onboarding/src/components/Confetti/Confetti.tsx b/code/addons/onboarding/src/components/Confetti/Confetti.tsx index e5e04628193d..90604bd1ce05 100644 --- a/code/addons/onboarding/src/components/Confetti/Confetti.tsx +++ b/code/addons/onboarding/src/components/Confetti/Confetti.tsx @@ -1,8 +1,19 @@ import React, { type ComponentProps, useEffect } from 'react'; import { useState } from 'react'; +import { styled } from 'storybook/internal/theming'; + import ReactConfetti from 'react-confetti-boom'; +const Wrapper = styled.div({ + zIndex: 9999, + position: 'fixed', + top: 0, + left: 0, + bottom: 0, + right: 0, +}); + export function Confetti({ timeToFade = 5000, colors = ['#CA90FF', '#FC521F', '#66BF3C', '#FF4785', '#FFAE00', '#1EA7FD'], @@ -21,13 +32,15 @@ export function Confetti({ }, [timeToFade]); return ( - + + + ); } From 2b2b54d37629a1758b87ead29151702bd3701700 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Wed, 18 Dec 2024 11:18:10 +0100 Subject: [PATCH 5/7] rename e2e test --- code/e2e-tests/addon-onboarding.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/e2e-tests/addon-onboarding.spec.ts b/code/e2e-tests/addon-onboarding.spec.ts index 64e10e269d9d..d3d6b951eeff 100644 --- a/code/e2e-tests/addon-onboarding.spec.ts +++ b/code/e2e-tests/addon-onboarding.spec.ts @@ -17,7 +17,7 @@ test.describe('addon-onboarding', () => { !supportsOnboarding, `Skipping ${templateName}, which does not have addon-onboarding set up.` ); - test('should load the onboarding screen', async ({ page }) => { + test('the onboarding flow', async ({ page }) => { await page.goto(`${storybookUrl}/?path=/onboarding`); const sbPage = new SbPage(page, expect); await sbPage.waitUntilLoaded(); From 441ad868861c8f3cf1eda4d030399caaae623d59 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Wed, 18 Dec 2024 12:08:50 +0100 Subject: [PATCH 6/7] skip onboarding addon tests for built storybooks --- code/e2e-tests/addon-onboarding.spec.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/code/e2e-tests/addon-onboarding.spec.ts b/code/e2e-tests/addon-onboarding.spec.ts index d3d6b951eeff..85181d8abf2c 100644 --- a/code/e2e-tests/addon-onboarding.spec.ts +++ b/code/e2e-tests/addon-onboarding.spec.ts @@ -5,6 +5,7 @@ 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') || @@ -13,6 +14,7 @@ const supportsOnboarding = 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.` From 717e2563cf9c52dafbdd4f9d20873d021cc31f99 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Wed, 18 Dec 2024 12:17:25 +0100 Subject: [PATCH 7/7] use @neoconfetti/react instead --- code/addons/onboarding/package.json | 2 +- .../src/components/Confetti/Confetti.tsx | 36 +++++++------------ code/yarn.lock | 19 +++++----- 3 files changed, 21 insertions(+), 36 deletions(-) diff --git a/code/addons/onboarding/package.json b/code/addons/onboarding/package.json index 0a92fbc89396..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-boom": "^1.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/components/Confetti/Confetti.tsx b/code/addons/onboarding/src/components/Confetti/Confetti.tsx index 90604bd1ce05..cebc40454909 100644 --- a/code/addons/onboarding/src/components/Confetti/Confetti.tsx +++ b/code/addons/onboarding/src/components/Confetti/Confetti.tsx @@ -1,46 +1,34 @@ -import React, { type ComponentProps, useEffect } from 'react'; -import { useState } from 'react'; +import React, { type ComponentProps } from 'react'; import { styled } from 'storybook/internal/theming'; -import ReactConfetti from 'react-confetti-boom'; +import { Confetti as ReactConfetti } from '@neoconfetti/react'; const Wrapper = styled.div({ zIndex: 9999, position: 'fixed', top: 0, - left: 0, - bottom: 0, - right: 0, + left: '50%', + width: '50%', + height: '100%', }); -export function Confetti({ +export const Confetti = React.memo(function Confetti({ timeToFade = 5000, colors = ['#CA90FF', '#FC521F', '#66BF3C', '#FF4785', '#FFAE00', '#1EA7FD'], ...confettiProps }: ComponentProps & { timeToFade?: number }) { - const [particleCount, setParticleCount] = useState(42); - - useEffect(() => { - const timeout = setTimeout(() => { - setParticleCount(0); - }, timeToFade); - - return () => { - clearTimeout(timeout); - }; - }, [timeToFade]); - return ( ); -} +}); diff --git a/code/yarn.lock b/code/yarn.lock index 5c1f23864a42..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-boom: "npm:^1.1.0" react-dom: "npm:^18.2.0" react-joyride: "npm:^2.8.2" react-use-measure: "npm:^2.1.1" @@ -24227,16 +24234,6 @@ __metadata: languageName: node linkType: hard -"react-confetti-boom@npm:^1.1.0": - version: 1.1.0 - resolution: "react-confetti-boom@npm:1.1.0" - peerDependencies: - react: ">=16.8.0" - react-dom: ">=16.8.0" - checksum: 10c0/76faf3e5cc3284b7dc0297ab7189ff92bbf74379a9478096bc8c8d7b16a82badea74c2e57f7313cd2a55ccf3c8dc50d51165fbf00c9fee5b9ed241369812d686 - languageName: node - linkType: hard - "react-confetti@npm:^6.1.0": version: 6.1.0 resolution: "react-confetti@npm:6.1.0"