Skip to content

Commit

Permalink
Merge pull request #26176 from storybookjs/monorepo-migration
Browse files Browse the repository at this point in the history
Addon-onboarding: Move onboarding to monorepo
  • Loading branch information
ndelangen authored Feb 27, 2024
2 parents 153a8f1 + e03e42e commit 02dbecf
Show file tree
Hide file tree
Showing 65 changed files with 4,511 additions and 484 deletions.
686 changes: 686 additions & 0 deletions code/addons/onboarding/CHANGELOG.md

Large diffs are not rendered by default.

50 changes: 50 additions & 0 deletions code/addons/onboarding/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Storybook Addon Onboarding

This addon provides a guided tour in some of Storybook's features, helping you get to know about the basics of Storybook and learn how to write stories!

![](./.github/assets/onboarding-intro.png)

## Triggering the onboarding

This addon comes installed by default in Storybook projects and should trigger automatically.
If you want to retrigger the addon, you should make sure that your Storybook still contains the example stories that come when initializing Storybook, and you can then navigate to http://localhost:6006/?path=/onboarding after running Storybook.

## Uninstalling

This addon serves to provide you a guided experience on the basics of Storybook. Once you are done, the addon is therefore not needed anymore and will not get activated (unless triggered manually), so you can freely remove it. Here's how to do so:

### 1. Remove the dependency

yarn:

```zsh
yarn remove @storybook/addon-onboarding
```

npm:

```zsh
npm uninstall -D @storybook/addon-onboarding
```

pnpm:

```zsh
pnpm remove -D @storybook/addon-onboarding
```

### 2. Remove the addon in your `.storybook/main.js` file

```diff
const config = {
stories: [
"../stories/**/*.stories.mdx",
"../stories/**/*.stories.@(js|jsx|ts|tsx)",
],
addons: [
"@storybook/addon-essentials",
- "@storybook/addon-onboarding"
],
};
export default config;
```
83 changes: 83 additions & 0 deletions code/addons/onboarding/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
{
"name": "@storybook/addon-onboarding",
"version": "8.0.0-beta.5",
"description": "Storybook Addon Onboarding - Introduces a new onboarding experience",
"keywords": [
"storybook-addons",
"addon-onboarding"
],
"homepage": "https://github.com/storybookjs/storybook/tree/next/code/addons/onboarding",
"bugs": {
"url": "https://github.com/storybookjs/storybook/issues"
},
"repository": {
"type": "git",
"url": "https://github.com/storybookjs/storybook.git",
"directory": "code/addons/onboarding"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/storybook"
},
"license": "MIT",
"exports": {
".": {
"types": "./dist/index.d.ts",
"node": "./dist/index.js",
"require": "./dist/index.js",
"import": "./dist/index.mjs"
},
"./manager": "./dist/manager.js",
"./preset": "./dist/preset.js",
"./package.json": "./package.json"
},
"main": "dist/index.js",
"module": "dist/index.mjs",
"types": "dist/index.d.ts",
"files": [
"dist/**/*",
"README.md",
"*.js",
"*.d.ts",
"!src/**/*"
],
"scripts": {
"check": "node --loader ../../../scripts/node_modules/esbuild-register/loader.js -r ../../../scripts/node_modules/esbuild-register/register.js ../../../scripts/prepare/check.ts",
"prep": "node --loader ../../../scripts/node_modules/esbuild-register/loader.js -r ../../../scripts/node_modules/esbuild-register/register.js ../../../scripts/prepare/addon-bundle.ts"
},
"devDependencies": {
"@radix-ui/react-dialog": "^1.0.5",
"@storybook/channels": "workspace:*",
"@storybook/components": "workspace:*",
"@storybook/core-events": "workspace:*",
"@storybook/icons": "^1.2.5",
"@storybook/manager-api": "workspace:*",
"@storybook/react": "workspace:*",
"@storybook/telemetry": "workspace:*",
"@storybook/test": "workspace:*",
"@storybook/testing-library": "next",
"@storybook/theming": "workspace:*",
"@storybook/types": "workspace:*",
"framer-motion": "^11.0.3",
"react": "^18.2.0",
"react-confetti": "^6.1.0",
"react-dom": "^18.2.0",
"react-joyride": "^2.7.2",
"react-use-measure": "^2.1.1",
"typescript": "^5.3.2"
},
"publishConfig": {
"access": "public"
},
"bundler": {
"exportEntries": [
"./src/index.ts"
],
"managerEntries": [
"./src/manager.tsx"
],
"nodeEntries": [
"./src/preset.ts"
]
}
}
8 changes: 8 additions & 0 deletions code/addons/onboarding/preset.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
function managerEntries(entry = []) {
return [...entry, require.resolve('./dist/manager.mjs')];
}

module.exports = {
managerEntries,
...require('./dist/preset'),
};
6 changes: 6 additions & 0 deletions code/addons/onboarding/project.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"name": "@storybook/addon-onboarding",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"implicitDependencies": [],
"type": "library"
}
155 changes: 155 additions & 0 deletions code/addons/onboarding/src/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
import React, { useCallback, useEffect, useState } from 'react';
import { ThemeProvider, convert } from '@storybook/theming';
import { addons, type API } from '@storybook/manager-api';

import { GuidedTour } from './features/GuidedTour/GuidedTour';
import { WelcomeModal } from './features/WelcomeModal/WelcomeModal';
import { WriteStoriesModal } from './features/WriteStoriesModal/WriteStoriesModal';
import { Confetti } from './components/Confetti/Confetti';
import { STORYBOOK_ADDON_ONBOARDING_CHANNEL } from './constants';
import { useGetProject } from './features/WriteStoriesModal/hooks/useGetProject';

type Step =
| '1:Welcome'
| '2:StorybookTour'
| '3:WriteYourStory'
| '4:VisitNewStory'
| '5:ConfigureYourProject';

const theme = convert();

export default function App({ api }: { api: API }) {
const [enabled, setEnabled] = useState(true);
const [showConfetti, setShowConfetti] = useState(false);
const [step, setStep] = useState<Step>('1:Welcome');
const { data: codeSnippets } = useGetProject();

const skipOnboarding = useCallback(() => {
// remove onboarding query parameter from current url
const url = new URL(window.location.href);
// @ts-expect-error (not strict)
const path = decodeURIComponent(url.searchParams.get('path'));
url.search = `?path=${path}&onboarding=false`;
history.replaceState({}, '', url.href);
api.setQueryParams({ onboarding: 'false' });
setEnabled(false);
}, [setEnabled, api]);

useEffect(() => {
api.emit(STORYBOOK_ADDON_ONBOARDING_CHANNEL, {
step: '1:Welcome',
type: 'telemetry',
});
}, []);

useEffect(() => {
if (step !== '1:Welcome') {
api.emit(STORYBOOK_ADDON_ONBOARDING_CHANNEL, {
step,
type: 'telemetry',
});
}
}, [api, step]);

useEffect(() => {
let stepTimeout: number;
if (step === '4:VisitNewStory') {
setShowConfetti(true);
stepTimeout = window.setTimeout(() => {
setStep('5:ConfigureYourProject');
}, 2000);
}

return () => {
clearTimeout(stepTimeout);
};
}, [step]);

useEffect(() => {
const storyId = api.getCurrentStoryData()?.id;
api.setQueryParams({ onboarding: 'true' });
// make sure the initial state is set correctly:
// 1. Selected story is primary button
// 2. The addon panel is opened, in the bottom and the controls tab is selected
if (storyId !== 'example-button--primary') {
try {
api.selectStory('example-button--primary', undefined, {
ref: undefined,
});
} catch (e) {
//
}
}
}, []);

if (!enabled) {
return null;
}

return (
<ThemeProvider theme={theme}>
{enabled && showConfetti && (
<Confetti
numberOfPieces={800}
recycle={false}
tweenDuration={20000}
onConfettiComplete={(confetti) => {
confetti?.reset();
setShowConfetti(false);
}}
/>
)}
{enabled && step === '1:Welcome' && (
<WelcomeModal
onProceed={() => {
setStep('2:StorybookTour');
}}
skipOnboarding={() => {
skipOnboarding();

api.emit(STORYBOOK_ADDON_ONBOARDING_CHANNEL, {
step: 'X:SkippedOnboarding',
where: 'WelcomeModal',
type: 'telemetry',
});
}}
/>
)}
{enabled && (step === '2:StorybookTour' || step === '5:ConfigureYourProject') && (
<GuidedTour
api={api}
isFinalStep={step === '5:ConfigureYourProject'}
onFirstTourDone={() => {
setStep('3:WriteYourStory');
}}
codeSnippets={codeSnippets || undefined}
onLastTourDone={() => {
try {
api.selectStory('configure-your-project--docs');
} catch (e) {
//
}
api.emit(STORYBOOK_ADDON_ONBOARDING_CHANNEL, {
step: '6:FinishedOnboarding',
type: 'telemetry',
});
skipOnboarding();
}}
/>
)}
{enabled && step === '3:WriteYourStory' && codeSnippets && (
<WriteStoriesModal
api={api}
codeSnippets={codeSnippets}
addonsStore={addons}
onFinish={() => {
api.selectStory('example-button--warning');

setStep('4:VisitNewStory');
}}
skipOnboarding={skipOnboarding}
/>
)}
</ThemeProvider>
);
}
15 changes: 15 additions & 0 deletions code/addons/onboarding/src/components/Button/Button.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import type { Meta, StoryObj } from '@storybook/react';
import { Button } from './Button';

const meta: Meta<typeof Button> = {
title: 'Components/Button',
component: Button,
};

export default meta;

type Story = StoryObj<typeof Button>;

export const Default: Story = {
args: { children: 'Button' },
};
76 changes: 76 additions & 0 deletions code/addons/onboarding/src/components/Button/Button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import type { ComponentProps } from 'react';
import React, { forwardRef } from 'react';
import { styled } from '@storybook/theming';

export interface ButtonProps extends ComponentProps<'button'> {
children: string;
onClick?: (e: React.MouseEvent<HTMLElement, MouseEvent>) => void;
variant?: 'primary' | 'secondary' | 'outline';
}

const StyledButton = styled.button<{ variant: ButtonProps['variant'] }>`
all: unset;
box-sizing: border-box;
border: 0;
border-radius: 0.25rem;
cursor: pointer;
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0 0.75rem;
background: ${({ theme, variant }) => {
if (variant === 'primary') return theme.color.secondary;
if (variant === 'secondary') return theme.color.lighter;
if (variant === 'outline') return 'transparent';
return theme.color.secondary;
}};
color: ${({ theme, variant }) => {
if (variant === 'primary') return theme.color.lightest;
if (variant === 'secondary') return theme.darkest;
if (variant === 'outline') return theme.darkest;
return theme.color.lightest;
}};
box-shadow: ${({ variant }) => {
if (variant === 'primary') return 'none';
if (variant === 'secondary') return '#D9E8F2 0 0 0 1px inset';
if (variant === 'outline') return '#D9E8F2 0 0 0 1px inset';
return 'none';
}};
height: 32px;
font-size: 0.8125rem;
font-weight: 700;
font-family: ${({ theme }) => theme.typography.fonts.base};
transition: background-color, box-shadow, opacity;
transition-duration: 0.16s;
transition-timing-function: ease-in-out;
text-decoration: none;
&:hover {
background-color: ${({ variant }) => {
if (variant === 'primary') return '#0b94eb';
if (variant === 'secondary') return '#eef4f9';
if (variant === 'outline') return 'transparent';
return '#0b94eb';
}};
}
&:focus {
box-shadow: ${({ variant }) => {
if (variant === 'primary') return 'inset 0 0 0 1px rgba(0, 0, 0, 0.2)';
if (variant === 'secondary') return 'inset 0 0 0 1px #0b94eb';
if (variant === 'outline') return 'inset 0 0 0 1px #0b94eb';
return 'inset 0 0 0 2px rgba(0, 0, 0, 0.1)';
}};
}
`;

export const Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button(
{ children, onClick, variant = 'primary', ...rest },
ref
) {
return (
<StyledButton ref={ref} onClick={onClick} variant={variant} {...rest}>
{children}
</StyledButton>
);
});
Loading

0 comments on commit 02dbecf

Please sign in to comment.