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/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/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/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"