Skip to content

Commit

Permalink
Merge pull request #30098 from storybookjs/norbert/fix-confetti-bundling
Browse files Browse the repository at this point in the history
Onboarding: Replace `react-confetti` with `@neoconfetti/react`
  • Loading branch information
yannbf authored Dec 19, 2024
2 parents abe4c88 + f202d2a commit 6f04c27
Show file tree
Hide file tree
Showing 10 changed files with 118 additions and 230 deletions.
2 changes: 1 addition & 1 deletion code/addons/onboarding/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
12 changes: 1 addition & 11 deletions code/addons/onboarding/src/Onboarding.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -268,17 +268,7 @@ export default function Onboarding({ api }: { api: API }) {

return (
<ThemeProvider theme={theme}>
{showConfetti && (
<Confetti
numberOfPieces={800}
recycle={false}
tweenDuration={20000}
onConfettiComplete={(confetti) => {
confetti?.reset();
setShowConfetti(false);
}}
/>
)}
{showConfetti && <Confetti />}
{step === '1:Intro' ? (
<SplashScreen onDismiss={() => setStep('2:Controls')} />
) : (
Expand Down
51 changes: 11 additions & 40 deletions code/addons/onboarding/src/components/Confetti/Confetti.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,19 @@ const meta: Meta<typeof Confetti> = {
component: Confetti,
parameters: {
chromatic: { disableSnapshot: true },
layout: 'fullscreen',
},
decorators: [
(StoryFn) => (
<div style={{ height: '100vh', width: '100vw' }}>
<button>I am clickable</button>
<div
style={{
height: '100vh',
width: '100vw',
alignContent: 'center',
textAlign: 'center',
}}
>
<span>Falling confetti! 🎉</span>
<StoryFn />
</div>
),
Expand All @@ -23,41 +31,4 @@ export default meta;

type Story = StoryObj<typeof Confetti>;

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 = {};
149 changes: 26 additions & 123 deletions code/addons/onboarding/src/components/Confetti/Confetti.tsx
Original file line number Diff line number Diff line change
@@ -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<React.ComponentProps<typeof ReactConfetti>, '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(
<Wrapper top={top} left={left} width={width} height={height}>
<ReactConfetti colors={colors} drawShape={draw} {...confettiProps} />
</Wrapper>,
confettiContainer
}: ComponentProps<typeof ReactConfetti> & { timeToFade?: number }) {
return (
<Wrapper>
<ReactConfetti
colors={colors}
particleCount={200}
duration={5000}
stageHeight={window.innerHeight}
stageWidth={window.innerWidth}
destroyAfterDone
{...confettiProps}
/>
</Wrapper>
);
}

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();
}
});
2 changes: 1 addition & 1 deletion code/core/assets/server/addon.tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@
"jsx": "react",
"jsxImportSource": "react"
}
}
}
53 changes: 53 additions & 0 deletions code/e2e-tests/addon-onboarding.spec.ts
Original file line number Diff line number Diff line change
@@ -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();
});
});
32 changes: 7 additions & 25 deletions code/frameworks/angular/src/builders/build-storybook/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -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
},
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -168,11 +156,7 @@
}
},
"additionalProperties": false,
"required": [
"glob",
"input",
"output"
]
"required": ["glob", "input", "output"]
},
{
"type": "string"
Expand Down Expand Up @@ -200,9 +184,7 @@
}
},
"additionalProperties": false,
"required": [
"input"
]
"required": ["input"]
},
{
"type": "string",
Expand All @@ -211,4 +193,4 @@
]
}
}
}
}
Loading

0 comments on commit 6f04c27

Please sign in to comment.