Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Onboarding: Replace react-confetti with @neoconfetti/react #30098

Merged
merged 9 commits into from
Dec 19, 2024
Merged
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
Loading