diff --git a/CHANGELOG.prerelease.md b/CHANGELOG.prerelease.md index 30572cbb308c..02449fd291ce 100644 --- a/CHANGELOG.prerelease.md +++ b/CHANGELOG.prerelease.md @@ -1,3 +1,28 @@ +## 8.0.0-beta.6 + +- Addon-onboarding: Move onboarding to monorepo - [#26176](https://github.com/storybookjs/storybook/pull/26176), thanks [@ndelangen](https://github.com/ndelangen)! +- Angular: Fix Storybook startup after init - [#26213](https://github.com/storybookjs/storybook/pull/26213), thanks [@valentinpalkovic](https://github.com/valentinpalkovic)! +- Angular: Use dedicated tsconfig for compodocs - [#26214](https://github.com/storybookjs/storybook/pull/26214), thanks [@kasperpeulen](https://github.com/kasperpeulen)! +- Automigration: Fix overflow bug by using a better link - [#26203](https://github.com/storybookjs/storybook/pull/26203), thanks [@kasperpeulen](https://github.com/kasperpeulen)! +- Automigration: Run the `mdx-to-csf` codemod during automigration - [#26201](https://github.com/storybookjs/storybook/pull/26201), thanks [@ndelangen](https://github.com/ndelangen)! +- Automigrations: Create addon-postcss automigration - [#26228](https://github.com/storybookjs/storybook/pull/26228), thanks [@valentinpalkovic](https://github.com/valentinpalkovic)! +- Automigrations: Enhance experience for upgrades from Storybook 6 to 8 - [#26067](https://github.com/storybookjs/storybook/pull/26067), thanks [@valentinpalkovic](https://github.com/valentinpalkovic)! +- Automigrations: General fixes for automigrations, blockers and codemods - [#26210](https://github.com/storybookjs/storybook/pull/26210), thanks [@valentinpalkovic](https://github.com/valentinpalkovic)! +- Blocks: Fix `Stories` block rendering duplicate stories when globals are changed - [#26110](https://github.com/storybookjs/storybook/pull/26110), thanks [@JReinhold](https://github.com/JReinhold)! +- Builder Vite: Pass on errors about not having a real stats object in smoke tests - [#26195](https://github.com/storybookjs/storybook/pull/26195), thanks [@tmeasday](https://github.com/tmeasday)! +- CLI: Rename `--webpack-stats-json` to `--stats-json` as it works in Vite - [#26128](https://github.com/storybookjs/storybook/pull/26128), thanks [@tmeasday](https://github.com/tmeasday)! +- Codemods: Enhance mdx-to-csf codemod - [#26164](https://github.com/storybookjs/storybook/pull/26164), thanks [@valentinpalkovic](https://github.com/valentinpalkovic)! +- Core: Export preview symbols for embedding - [#26224](https://github.com/storybookjs/storybook/pull/26224), thanks [@tmeasday](https://github.com/tmeasday)! +- Docs: Don't show how to setup controls for empty argTypes in doc pages - [#26200](https://github.com/storybookjs/storybook/pull/26200), thanks [@kasperpeulen](https://github.com/kasperpeulen)! +- Docs: Fix function prop rendering as `noRefCheck` in storybook docs - [#26104](https://github.com/storybookjs/storybook/pull/26104), thanks [@thisisanto](https://github.com/thisisanto)! +- Doctor: Add dynamic check for incompatible Storybook packages - [#26219](https://github.com/storybookjs/storybook/pull/26219), thanks [@yannbf](https://github.com/yannbf)! +- Maintenance: Remove deprecation of `manager-api`'s `types` export - [#26202](https://github.com/storybookjs/storybook/pull/26202), thanks [@ndelangen](https://github.com/ndelangen)! +- Revert "Revert: "Angular: Reduce the warnings from `ts-loader` via stricter list of `includes`"" - [#26226](https://github.com/storybookjs/storybook/pull/26226), thanks [@ndelangen](https://github.com/ndelangen)! +- Revert: "Angular: Reduce the warnings from `ts-loader` via stricter list of `includes`" - [#26208](https://github.com/storybookjs/storybook/pull/26208), thanks [@kasperpeulen](https://github.com/kasperpeulen)! +- UI: Improve sidebar performance when switching stories - [#26184](https://github.com/storybookjs/storybook/pull/26184), thanks [@literalpie](https://github.com/literalpie)! +- UI: Update theme switcher to use toggle button for two themes - [#25682](https://github.com/storybookjs/storybook/pull/25682), thanks [@ivoilic](https://github.com/ivoilic)! +- Vue: Improve types for array, union and intersection when using vue-docgen-api - [#26177](https://github.com/storybookjs/storybook/pull/26177), thanks [@larsrickert](https://github.com/larsrickert)! + ## 8.0.0-beta.5 - Addon-controls: Dont show "setup controls" if control is disabled or a function - [#26120](https://github.com/storybookjs/storybook/pull/26120), thanks [@kasperpeulen](https://github.com/kasperpeulen)! diff --git a/MIGRATION.md b/MIGRATION.md index 77d77356d46e..202962c2cf7f 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -15,6 +15,7 @@ - [For Solid:](#for-solid) - [For Qwik:](#for-qwik) - [TurboSnap Vite plugin is no longer needed](#turbosnap-vite-plugin-is-no-longer-needed) + - [`--webpack-stats-json` option renamed `--stats-json`](#--webpack-stats-json-option-renamed---stats-json) - [Implicit actions can not be used during rendering (for example in the play function)](#implicit-actions-can-not-be-used-during-rendering-for-example-in-the-play-function) - [MDX related changes](#mdx-related-changes) - [MDX is upgraded to v3](#mdx-is-upgraded-to-v3) @@ -551,6 +552,11 @@ At least in build mode, `builder-vite` now supports the `--webpack-stats-json` f This means https://github.com/IanVS/vite-plugin-turbosnap is no longer necessary, and duplicative, and the plugin will automatically be removed if found. +### `--webpack-stats-json` option renamed `--stats-json` + +Now that both Vite and Webpack support the `preview-stats.json` file, the flag has been renamed. The old flag will continue to work. + + ### Implicit actions can not be used during rendering (for example in the play function) In Storybook 7, we inferred if the component accepts any action props, diff --git a/code/addons/docs/template/stories/docs2/multiple-csf-files-a.stories.ts b/code/addons/docs/template/stories/docs2/multiple-csf-files-a.stories.ts new file mode 100644 index 000000000000..c77284a296f3 --- /dev/null +++ b/code/addons/docs/template/stories/docs2/multiple-csf-files-a.stories.ts @@ -0,0 +1,23 @@ +import { global as globalThis } from '@storybook/global'; + +export default { + title: 'Multiple CSF Files Same Title', + component: globalThis.Components.Html, + tags: ['autodocs'], + args: { + content: '

paragraph

', + }, + parameters: { + chromatic: { disable: true }, + }, +}; + +export const DefaultA = {}; + +export const SpanContent = { + args: { content: 'span' }, +}; + +export const CodeContent = { + args: { content: 'code' }, +}; diff --git a/code/addons/docs/template/stories/docs2/multiple-csf-files-b.stories.ts b/code/addons/docs/template/stories/docs2/multiple-csf-files-b.stories.ts new file mode 100644 index 000000000000..955c04af9f9e --- /dev/null +++ b/code/addons/docs/template/stories/docs2/multiple-csf-files-b.stories.ts @@ -0,0 +1,23 @@ +import { global as globalThis } from '@storybook/global'; + +export default { + title: 'Multiple CSF Files Same Title', + component: globalThis.Components.Html, + tags: ['autodocs'], + args: { + content: '

paragraph

', + }, + parameters: { + chromatic: { disable: true }, + }, +}; + +export const DefaultB = {}; + +export const H1Content = { + args: { content: '

heading 1

' }, +}; + +export const H2Content = { + args: { content: '

heading 2

' }, +}; diff --git a/code/addons/onboarding/CHANGELOG.md b/code/addons/onboarding/CHANGELOG.md new file mode 100644 index 000000000000..16a9c57bea11 --- /dev/null +++ b/code/addons/onboarding/CHANGELOG.md @@ -0,0 +1,686 @@ +# v1.0.11 (Tue Jan 23 2024) + +#### 🐛 Bug Fix + +- Fix z-index bug by adding a wrapper [#82](https://github.com/storybookjs/addon-onboarding/pull/82) ([@ndelangen](https://github.com/ndelangen)) +- Make selectors Storybook 8 compatible [#81](https://github.com/storybookjs/addon-onboarding/pull/81) ([@yannbf](https://github.com/yannbf)) +- UI: Fix z-index in modal elements [#78](https://github.com/storybookjs/addon-onboarding/pull/78) ([@cdedreuille](https://github.com/cdedreuille) [@yannbf](https://github.com/yannbf)) + +#### Authors: 3 + +- Charles de Dreuille ([@cdedreuille](https://github.com/cdedreuille)) +- Norbert de Langen ([@ndelangen](https://github.com/ndelangen)) +- Yann Braga ([@yannbf](https://github.com/yannbf)) + +--- + +# v1.0.10 (Mon Dec 11 2023) + +#### 🐛 Bug Fix + +- Fix Yarn remove command in README [#80](https://github.com/storybookjs/addon-onboarding/pull/80) ([@githrdw](https://github.com/githrdw)) + +#### Authors: 1 + +- [@githrdw](https://github.com/githrdw) + +--- + +# v1.0.9 (Fri Dec 01 2023) + +#### 🐛 Bug Fix + +- update telemetry version [#79](https://github.com/storybookjs/addon-onboarding/pull/79) ([@yannbf](https://github.com/yannbf)) + +#### Authors: 1 + +- Yann Braga ([@yannbf](https://github.com/yannbf)) + +--- + +# v1.0.8 (Thu Jul 20 2023) + +#### ⚠️ Pushed to `main` + +- Create CODEOWNERS ([@valentinpalkovic](https://github.com/valentinpalkovic)) + +#### Authors: 1 + +- Valentin Palkovic ([@valentinpalkovic](https://github.com/valentinpalkovic)) + +--- + +# v1.0.7 (Tue Jul 11 2023) + +#### 🐛 Bug Fix + +- Replace chevron icon on Configure page to avoid @storybook/components usage [#77](https://github.com/storybookjs/addon-onboarding/pull/77) ([@valentinpalkovic](https://github.com/valentinpalkovic)) + +#### Authors: 1 + +- Valentin Palkovic ([@valentinpalkovic](https://github.com/valentinpalkovic)) + +--- + +# v1.0.6 (Mon Jul 10 2023) + +#### 🐛 Bug Fix + +- Fix language detection [#76](https://github.com/storybookjs/addon-onboarding/pull/76) ([@valentinpalkovic](https://github.com/valentinpalkovic)) + +#### Authors: 1 + +- Valentin Palkovic ([@valentinpalkovic](https://github.com/valentinpalkovic)) + +--- + +# v1.0.5 (Mon Jul 10 2023) + +#### 🐛 Bug Fix + +- Remove nextjs-specific code [#75](https://github.com/storybookjs/addon-onboarding/pull/75) ([@yannbf](https://github.com/yannbf)) + +#### Authors: 1 + +- Yann Braga ([@yannbf](https://github.com/yannbf)) + +--- + +# v1.0.4 (Thu Jul 06 2023) + +#### 🐛 Bug Fix + +- Update Configure page design [#74](https://github.com/storybookjs/addon-onboarding/pull/74) ([@JReinhold](https://github.com/JReinhold) [@cdedreuille](https://github.com/cdedreuille)) + +#### Authors: 2 + +- Charles de Dreuille ([@cdedreuille](https://github.com/cdedreuille)) +- Jeppe Reinhold ([@JReinhold](https://github.com/JReinhold)) + +--- + +# v1.0.3 (Fri Jun 23 2023) + +#### 🐛 Bug Fix + +- Display the button story filename in tooltip [#73](https://github.com/storybookjs/addon-onboarding/pull/73) ([@yannbf](https://github.com/yannbf)) + +#### Authors: 1 + +- Yann Braga ([@yannbf](https://github.com/yannbf)) + +--- + +# v1.0.2 (Thu Jun 22 2023) + +#### 🐛 Bug Fix + +- Fix package.json version extraction [#72](https://github.com/storybookjs/addon-onboarding/pull/72) ([@valentinpalkovic](https://github.com/valentinpalkovic)) + +#### Authors: 1 + +- Valentin Palkovic ([@valentinpalkovic](https://github.com/valentinpalkovic)) + +--- + +# v1.0.1 (Thu Jun 22 2023) + +#### 🐛 Bug Fix + +- Fix build assets [#71](https://github.com/storybookjs/addon-onboarding/pull/71) ([@valentinpalkovic](https://github.com/valentinpalkovic)) + +#### Authors: 1 + +- Valentin Palkovic ([@valentinpalkovic](https://github.com/valentinpalkovic)) + +--- + +# v1.0.0 (Thu Jun 22 2023) + +#### 💥 Breaking Change + +- Release 1.0.0 [#70](https://github.com/storybookjs/addon-onboarding/pull/70) ([@valentinpalkovic](https://github.com/valentinpalkovic)) + +#### 🐛 Bug Fix + +- Fix text for Javascript Projects [#68](https://github.com/storybookjs/addon-onboarding/pull/68) ([@valentinpalkovic](https://github.com/valentinpalkovic)) + +#### Authors: 1 + +- Valentin Palkovic ([@valentinpalkovic](https://github.com/valentinpalkovic)) + +--- + +# v0.0.43 (Thu Jun 22 2023) + +#### 🐛 Bug Fix + +- Fix react joyride in yarn pnp mode [#69](https://github.com/storybookjs/addon-onboarding/pull/69) ([@valentinpalkovic](https://github.com/valentinpalkovic)) +- fix links in configure.mdx [#67](https://github.com/storybookjs/addon-onboarding/pull/67) ([@yannbf](https://github.com/yannbf)) + +#### Authors: 2 + +- Valentin Palkovic ([@valentinpalkovic](https://github.com/valentinpalkovic)) +- Yann Braga ([@yannbf](https://github.com/yannbf)) + +--- + +# v0.0.42 (Wed Jun 21 2023) + +#### 🐛 Bug Fix + +- Fix nextjs code steps [#66](https://github.com/storybookjs/addon-onboarding/pull/66) ([@yannbf](https://github.com/yannbf)) + +#### Authors: 1 + +- Yann Braga ([@yannbf](https://github.com/yannbf)) + +--- + +# v0.0.41 (Wed Jun 21 2023) + +#### 🐛 Bug Fix + +- Minor improvements [#65](https://github.com/storybookjs/addon-onboarding/pull/65) ([@valentinpalkovic](https://github.com/valentinpalkovic)) + +#### Authors: 1 + +- Valentin Palkovic ([@valentinpalkovic](https://github.com/valentinpalkovic)) + +--- + +# v0.0.40 (Tue Jun 20 2023) + +#### 🐛 Bug Fix + +- Fix preset.js entry point [#63](https://github.com/storybookjs/addon-onboarding/pull/63) ([@valentinpalkovic](https://github.com/valentinpalkovic)) + +#### Authors: 1 + +- Valentin Palkovic ([@valentinpalkovic](https://github.com/valentinpalkovic)) + +--- + +# v0.0.39 (Tue Jun 20 2023) + +#### 🐛 Bug Fix + +- Fix comment in code section [#64](https://github.com/storybookjs/addon-onboarding/pull/64) ([@valentinpalkovic](https://github.com/valentinpalkovic)) + +#### Authors: 1 + +- Valentin Palkovic ([@valentinpalkovic](https://github.com/valentinpalkovic)) + +--- + +# v0.0.38 (Mon Jun 19 2023) + +#### 🐛 Bug Fix + +- Pass addon version in telemetry event [#62](https://github.com/storybookjs/addon-onboarding/pull/62) ([@yannbf](https://github.com/yannbf)) + +#### Authors: 1 + +- Yann Braga ([@yannbf](https://github.com/yannbf)) + +--- + +# v0.0.37 (Mon Jun 19 2023) + +#### 🐛 Bug Fix + +- UI Fixes [#61](https://github.com/storybookjs/addon-onboarding/pull/61) ([@yannbf](https://github.com/yannbf) [@cdedreuille](https://github.com/cdedreuille)) + +#### Authors: 2 + +- Charles de Dreuille ([@cdedreuille](https://github.com/cdedreuille)) +- Yann Braga ([@yannbf](https://github.com/yannbf)) + +--- + +# v0.0.36 (Fri Jun 16 2023) + +#### 🐛 Bug Fix + +- Improve dark mode [#58](https://github.com/storybookjs/addon-onboarding/pull/58) ([@cdedreuille](https://github.com/cdedreuille) [@yannbf](https://github.com/yannbf)) + +#### Authors: 2 + +- Charles de Dreuille ([@cdedreuille](https://github.com/cdedreuille)) +- Yann Braga ([@yannbf](https://github.com/yannbf)) + +--- + +# v0.0.35 (Fri Jun 16 2023) + +#### 🐛 Bug Fix + +- Add dark mode styles to tooltip [#54](https://github.com/storybookjs/addon-onboarding/pull/54) ([@yannbf](https://github.com/yannbf)) +- UI fixes [#56](https://github.com/storybookjs/addon-onboarding/pull/56) ([@yannbf](https://github.com/yannbf)) +- Fix watch mode [#55](https://github.com/storybookjs/addon-onboarding/pull/55) ([@yannbf](https://github.com/yannbf)) + +#### Authors: 1 + +- Yann Braga ([@yannbf](https://github.com/yannbf)) + +--- + +# v0.0.34 (Wed Jun 14 2023) + +#### 🐛 Bug Fix + +- Fix configure page's layout [#53](https://github.com/storybookjs/addon-onboarding/pull/53) ([@cdedreuille](https://github.com/cdedreuille)) + +#### Authors: 1 + +- Charles de Dreuille ([@cdedreuille](https://github.com/cdedreuille)) + +--- + +# v0.0.33 (Wed Jun 14 2023) + +#### 🐛 Bug Fix + +- Improve story detection [#52](https://github.com/storybookjs/addon-onboarding/pull/52) ([@yannbf](https://github.com/yannbf)) + +#### Authors: 1 + +- Yann Braga ([@yannbf](https://github.com/yannbf)) + +--- + +# v0.0.32 (Wed Jun 14 2023) + +#### 🐛 Bug Fix + +- Fixes on syntax highlighter [#51](https://github.com/storybookjs/addon-onboarding/pull/51) ([@cdedreuille](https://github.com/cdedreuille) [@yannbf](https://github.com/yannbf)) + +#### Authors: 2 + +- Charles de Dreuille ([@cdedreuille](https://github.com/cdedreuille)) +- Yann Braga ([@yannbf](https://github.com/yannbf)) + +--- + +# v0.0.31 (Wed Jun 14 2023) + +#### 🐛 Bug Fix + +- Improve instructions [#50](https://github.com/storybookjs/addon-onboarding/pull/50) ([@shilman](https://github.com/shilman)) + +#### Authors: 1 + +- Michael Shilman ([@shilman](https://github.com/shilman)) + +--- + +# v0.0.30 (Tue Jun 13 2023) + +#### 🐛 Bug Fix + +- Add core server events for telemetry [#40](https://github.com/storybookjs/addon-onboarding/pull/40) ([@valentinpalkovic](https://github.com/valentinpalkovic) [@yannbf](https://github.com/yannbf)) + +#### Authors: 2 + +- Valentin Palkovic ([@valentinpalkovic](https://github.com/valentinpalkovic)) +- Yann Braga ([@yannbf](https://github.com/yannbf)) + +--- + +# v0.0.29 (Tue Jun 13 2023) + +#### ⚠️ Pushed to `main` + +- cleanup dependencies ([@yannbf](https://github.com/yannbf)) + +#### Authors: 1 + +- Yann Braga ([@yannbf](https://github.com/yannbf)) + +--- + +# v0.0.28 (Mon Jun 12 2023) + +#### 🐛 Bug Fix + +- Feat/overall improvements [#49](https://github.com/storybookjs/addon-onboarding/pull/49) ([@yannbf](https://github.com/yannbf) [@cdedreuille](https://github.com/cdedreuille)) + +#### Authors: 2 + +- Charles de Dreuille ([@cdedreuille](https://github.com/cdedreuille)) +- Yann Braga ([@yannbf](https://github.com/yannbf)) + +--- + +# v0.0.27 (Fri Jun 09 2023) + +#### 🐛 Bug Fix + +- Add previous button to write stories modal [#48](https://github.com/storybookjs/addon-onboarding/pull/48) ([@yannbf](https://github.com/yannbf) [@cdedreuille](https://github.com/cdedreuille)) + +#### Authors: 2 + +- Charles de Dreuille ([@cdedreuille](https://github.com/cdedreuille)) +- Yann Braga ([@yannbf](https://github.com/yannbf)) + +--- + +# v0.0.26 (Thu Jun 08 2023) + +#### 🐛 Bug Fix + +- Remove animation modal [#47](https://github.com/storybookjs/addon-onboarding/pull/47) ([@cdedreuille](https://github.com/cdedreuille)) + +#### Authors: 1 + +- Charles de Dreuille ([@cdedreuille](https://github.com/cdedreuille)) + +--- + +# v0.0.25 (Thu Jun 08 2023) + +#### 🐛 Bug Fix + +- Improve addon bootstrapping [#46](https://github.com/storybookjs/addon-onboarding/pull/46) ([@yannbf](https://github.com/yannbf)) + +#### Authors: 1 + +- Yann Braga ([@yannbf](https://github.com/yannbf)) + +--- + +# v0.0.24 (Thu Jun 08 2023) + +#### 🐛 Bug Fix + +- Use the correct event to detect args change [#45](https://github.com/storybookjs/addon-onboarding/pull/45) ([@yannbf](https://github.com/yannbf)) + +#### Authors: 1 + +- Yann Braga ([@yannbf](https://github.com/yannbf)) + +--- + +# v0.0.23 (Thu Jun 08 2023) + +#### 🐛 Bug Fix + +- Various improvements [#44](https://github.com/storybookjs/addon-onboarding/pull/44) ([@yannbf](https://github.com/yannbf) [@cdedreuille](https://github.com/cdedreuille)) + +#### Authors: 2 + +- Charles de Dreuille ([@cdedreuille](https://github.com/cdedreuille)) +- Yann Braga ([@yannbf](https://github.com/yannbf)) + +--- + +# v0.0.22 (Thu Jun 08 2023) + +#### 🐛 Bug Fix + +- Improve confetti colors and shapes [#43](https://github.com/storybookjs/addon-onboarding/pull/43) ([@yannbf](https://github.com/yannbf) [@cdedreuille](https://github.com/cdedreuille)) + +#### Authors: 2 + +- Charles de Dreuille ([@cdedreuille](https://github.com/cdedreuille)) +- Yann Braga ([@yannbf](https://github.com/yannbf)) + +--- + +# v0.0.21 (Thu Jun 08 2023) + +#### 🐛 Bug Fix + +- Improve modal animation [#42](https://github.com/storybookjs/addon-onboarding/pull/42) ([@cdedreuille](https://github.com/cdedreuille)) + +#### Authors: 1 + +- Charles de Dreuille ([@cdedreuille](https://github.com/cdedreuille)) + +--- + +# v0.0.20 (Wed Jun 07 2023) + +#### 🐛 Bug Fix + +- Change code for Javascript projects [#41](https://github.com/storybookjs/addon-onboarding/pull/41) ([@valentinpalkovic](https://github.com/valentinpalkovic)) + +#### Authors: 1 + +- Valentin Palkovic ([@valentinpalkovic](https://github.com/valentinpalkovic)) + +--- + +# v0.0.19 (Wed Jun 07 2023) + +#### 🐛 Bug Fix + +- Improve write a story modal [#38](https://github.com/storybookjs/addon-onboarding/pull/38) ([@cdedreuille](https://github.com/cdedreuille)) + +#### Authors: 1 + +- Charles de Dreuille ([@cdedreuille](https://github.com/cdedreuille)) + +--- + +# v0.0.18 (Wed Jun 07 2023) + +#### 🐛 Bug Fix + +- Implement "How to write your Story" flow [#33](https://github.com/storybookjs/addon-onboarding/pull/33) ([@valentinpalkovic](https://github.com/valentinpalkovic)) + +#### Authors: 1 + +- Valentin Palkovic ([@valentinpalkovic](https://github.com/valentinpalkovic)) + +--- + +# v0.0.17 (Wed Jun 07 2023) + +#### 🐛 Bug Fix + +- use onboarding parameter in conjunction with onboarding path [#37](https://github.com/storybookjs/addon-onboarding/pull/37) ([@yannbf](https://github.com/yannbf)) + +#### Authors: 1 + +- Yann Braga ([@yannbf](https://github.com/yannbf)) + +--- + +# v0.0.16 (Tue Jun 06 2023) + +#### 🐛 Bug Fix + +- Improve guided tour [#35](https://github.com/storybookjs/addon-onboarding/pull/35) ([@cdedreuille](https://github.com/cdedreuille) [@yannbf](https://github.com/yannbf)) + +#### Authors: 2 + +- Charles de Dreuille ([@cdedreuille](https://github.com/cdedreuille)) +- Yann Braga ([@yannbf](https://github.com/yannbf)) + +--- + +# v0.0.15 (Tue Jun 06 2023) + +#### 🐛 Bug Fix + +- Trigger via `/onboarding` path instead of query parameter [#21](https://github.com/storybookjs/addon-onboarding/pull/21) ([@yannbf](https://github.com/yannbf)) +- Welcome modal animation [#34](https://github.com/storybookjs/addon-onboarding/pull/34) ([@cdedreuille](https://github.com/cdedreuille)) + +#### Authors: 2 + +- Charles de Dreuille ([@cdedreuille](https://github.com/cdedreuille)) +- Yann Braga ([@yannbf](https://github.com/yannbf)) + +--- + +# v0.0.14 (Mon Jun 05 2023) + +#### 🐛 Bug Fix + +- Modal improvements [#22](https://github.com/storybookjs/addon-onboarding/pull/22) ([@cdedreuille](https://github.com/cdedreuille)) + +#### Authors: 1 + +- Charles de Dreuille ([@cdedreuille](https://github.com/cdedreuille)) + +--- + +# v0.0.13 (Mon Jun 05 2023) + +#### 🐛 Bug Fix + +- Implement syntax highlighter [#20](https://github.com/storybookjs/addon-onboarding/pull/20) ([@valentinpalkovic](https://github.com/valentinpalkovic)) + +#### Authors: 1 + +- Valentin Palkovic ([@valentinpalkovic](https://github.com/valentinpalkovic)) + +--- + +# v0.0.12 (Thu Jun 01 2023) + +#### 🐛 Bug Fix + +- Implemented bare minimum list component [#19](https://github.com/storybookjs/addon-onboarding/pull/19) ([@valentinpalkovic](https://github.com/valentinpalkovic)) + +#### Authors: 1 + +- Valentin Palkovic ([@valentinpalkovic](https://github.com/valentinpalkovic)) + +--- + +# v0.0.11 (Thu Jun 01 2023) + +#### 🐛 Bug Fix + +- Add guided tour example [#5](https://github.com/storybookjs/addon-onboarding/pull/5) ([@yannbf](https://github.com/yannbf)) + +#### Authors: 1 + +- Yann Braga ([@yannbf](https://github.com/yannbf)) + +--- + +# v0.0.10 (Tue May 30 2023) + +#### ⚠️ Pushed to `main` + +- Rename confett to Confetti ([@valentinpalkovic](https://github.com/valentinpalkovic)) +- Rename confetti to confett ([@valentinpalkovic](https://github.com/valentinpalkovic)) + +#### Authors: 1 + +- Valentin Palkovic ([@valentinpalkovic](https://github.com/valentinpalkovic)) + +--- + +# v0.0.9 (Tue May 30 2023) + +#### 🐛 Bug Fix + +- Implement bare minimum modal component [#18](https://github.com/storybookjs/addon-onboarding/pull/18) ([@valentinpalkovic](https://github.com/valentinpalkovic)) + +#### Authors: 1 + +- Valentin Palkovic ([@valentinpalkovic](https://github.com/valentinpalkovic)) + +--- + +# v0.0.8 (Tue May 30 2023) + +#### 🐛 Bug Fix + +- Implement bare minimum confetti component [#7](https://github.com/storybookjs/addon-onboarding/pull/7) ([@valentinpalkovic](https://github.com/valentinpalkovic)) + +#### Authors: 1 + +- Valentin Palkovic ([@valentinpalkovic](https://github.com/valentinpalkovic)) + +--- + +# v0.0.7 (Thu May 25 2023) + +#### 🐛 Bug Fix + +- Init Storybook Theme Provider [#6](https://github.com/storybookjs/addon-onboarding/pull/6) ([@valentinpalkovic](https://github.com/valentinpalkovic)) + +#### Authors: 1 + +- Valentin Palkovic ([@valentinpalkovic](https://github.com/valentinpalkovic)) + +--- + +# v0.0.6 (Tue May 23 2023) + +#### 🐛 Bug Fix + +- Add minimum React Application [#3](https://github.com/storybookjs/addon-onboarding/pull/3) ([@valentinpalkovic](https://github.com/valentinpalkovic)) + +#### Authors: 1 + +- Valentin Palkovic ([@valentinpalkovic](https://github.com/valentinpalkovic)) + +--- + +# v0.0.5 (Tue May 23 2023) + +#### ⚠️ Pushed to `main` + +- Add clean package.json ([@valentinpalkovic](https://github.com/valentinpalkovic)) + +#### Authors: 1 + +- Valentin Palkovic ([@valentinpalkovic](https://github.com/valentinpalkovic)) + +--- + +# v0.0.4 (Tue May 23 2023) + +#### ⚠️ Pushed to `main` + +- Cleanup ([@valentinpalkovic](https://github.com/valentinpalkovic)) + +#### Authors: 1 + +- Valentin Palkovic ([@valentinpalkovic](https://github.com/valentinpalkovic)) + +--- + +# v0.0.3 (Tue May 23 2023) + +#### 🐛 Bug Fix + +- Setup Chromatic [#2](https://github.com/storybookjs/addon-onboarding/pull/2) ([@valentinpalkovic](https://github.com/valentinpalkovic)) + +#### Authors: 1 + +- Valentin Palkovic ([@valentinpalkovic](https://github.com/valentinpalkovic)) + +--- + +# v0.0.2 (Tue May 23 2023) + +#### 🐛 Bug Fix + +- Remove unnecessary files [#1](https://github.com/storybookjs/addon-onboarding/pull/1) ([@valentinpalkovic](https://github.com/valentinpalkovic)) + +#### Authors: 1 + +- Valentin Palkovic ([@valentinpalkovic](https://github.com/valentinpalkovic)) + +--- + +# v0.0.1 (Tue May 23 2023) + +#### ⚠️ Pushed to `main` + +- Fix manager.ts and add export {} ([@valentinpalkovic](https://github.com/valentinpalkovic)) +- Add auto.config.js ([@valentinpalkovic](https://github.com/valentinpalkovic)) +- Init Addon Onboarding ([@valentinpalkovic](https://github.com/valentinpalkovic)) +- project setup ([@valentinpalkovic](https://github.com/valentinpalkovic)) +- Initial commit ([@valentinpalkovic](https://github.com/valentinpalkovic)) + +#### Authors: 1 + +- Valentin Palkovic ([@valentinpalkovic](https://github.com/valentinpalkovic)) diff --git a/code/addons/onboarding/README.md b/code/addons/onboarding/README.md new file mode 100644 index 000000000000..257dd6a05627 --- /dev/null +++ b/code/addons/onboarding/README.md @@ -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; +``` diff --git a/code/addons/onboarding/package.json b/code/addons/onboarding/package.json new file mode 100644 index 000000000000..ee3f943427d6 --- /dev/null +++ b/code/addons/onboarding/package.json @@ -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" + ] + } +} diff --git a/code/addons/onboarding/preset.js b/code/addons/onboarding/preset.js new file mode 100644 index 000000000000..2c7ea670f4a6 --- /dev/null +++ b/code/addons/onboarding/preset.js @@ -0,0 +1,8 @@ +function managerEntries(entry = []) { + return [...entry, require.resolve('./dist/manager.mjs')]; +} + +module.exports = { + managerEntries, + ...require('./dist/preset'), +}; diff --git a/code/addons/onboarding/project.json b/code/addons/onboarding/project.json new file mode 100644 index 000000000000..1c9bf19c6931 --- /dev/null +++ b/code/addons/onboarding/project.json @@ -0,0 +1,6 @@ +{ + "name": "@storybook/addon-onboarding", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "implicitDependencies": [], + "type": "library" +} diff --git a/code/addons/onboarding/src/App.tsx b/code/addons/onboarding/src/App.tsx new file mode 100644 index 000000000000..e561c55cea1c --- /dev/null +++ b/code/addons/onboarding/src/App.tsx @@ -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('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 ( + + {enabled && showConfetti && ( + { + confetti?.reset(); + setShowConfetti(false); + }} + /> + )} + {enabled && step === '1:Welcome' && ( + { + 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') && ( + { + 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 && ( + { + api.selectStory('example-button--warning'); + + setStep('4:VisitNewStory'); + }} + skipOnboarding={skipOnboarding} + /> + )} + + ); +} diff --git a/code/addons/onboarding/src/components/Button/Button.stories.tsx b/code/addons/onboarding/src/components/Button/Button.stories.tsx new file mode 100644 index 000000000000..cf8a824713fb --- /dev/null +++ b/code/addons/onboarding/src/components/Button/Button.stories.tsx @@ -0,0 +1,15 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { Button } from './Button'; + +const meta: Meta = { + title: 'Components/Button', + component: Button, +}; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = { + args: { children: 'Button' }, +}; diff --git a/code/addons/onboarding/src/components/Button/Button.tsx b/code/addons/onboarding/src/components/Button/Button.tsx new file mode 100644 index 000000000000..622359c4e31a --- /dev/null +++ b/code/addons/onboarding/src/components/Button/Button.tsx @@ -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) => 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(function Button( + { children, onClick, variant = 'primary', ...rest }, + ref +) { + return ( + + {children} + + ); +}); diff --git a/code/addons/onboarding/src/components/Confetti/Confetti.stories.tsx b/code/addons/onboarding/src/components/Confetti/Confetti.stories.tsx new file mode 100644 index 000000000000..80b01c7e31d8 --- /dev/null +++ b/code/addons/onboarding/src/components/Confetti/Confetti.stories.tsx @@ -0,0 +1,61 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { Confetti } from './Confetti'; +import React from 'react'; + +const meta: Meta = { + component: Confetti, + parameters: { + chromatic: { disableSnapshot: true }, + }, + decorators: [ + (StoryFn) => ( +
+ + +
+ ), + ], +}; + +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, + }, +}; diff --git a/code/addons/onboarding/src/components/Confetti/Confetti.tsx b/code/addons/onboarding/src/components/Confetti/Confetti.tsx new file mode 100644 index 000000000000..5f0658dc462b --- /dev/null +++ b/code/addons/onboarding/src/components/Confetti/Confetti.tsx @@ -0,0 +1,129 @@ +import { ReactConfetti } from 'react-confetti/src/ReactConfetti'; +import React, { useEffect } from 'react'; +import { styled } from '@storybook/theming'; +import { createPortal } from 'react-dom'; +import { useState } from 'react'; + +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', +})); + +export function Confetti({ + top = 0, + left = 0, + width = window.innerWidth, + height = window.innerHeight, + 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 + ); +} + +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/addons/onboarding/src/components/List/List.stories.tsx b/code/addons/onboarding/src/components/List/List.stories.tsx new file mode 100644 index 000000000000..380fd07ca4cc --- /dev/null +++ b/code/addons/onboarding/src/components/List/List.stories.tsx @@ -0,0 +1,49 @@ +import React, { useState } from 'react'; +import type { Meta, StoryObj } from '@storybook/react'; +import { userEvent, waitFor, within } from '@storybook/testing-library'; +import { expect } from '@storybook/test'; + +import { List } from './List'; +import { ListItem } from './ListItem/ListItem'; + +const meta: Meta = { + component: List, +}; + +export default meta; + +export const Default: StoryObj = { + render: () => { + const [workingIndex, setWorkingIndex] = useState(1); + + return ( + <> + + = 1} index={1}> + Hello World + + = 2} index={2}> + Bonjour le monde + + = 3} index={3}> + 你好, 世界 + + +
+ + + ); + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement.parentElement!); + const button = canvas.getByText('Complete'); + + await expect(canvas.getAllByLabelText('complete')).toHaveLength(1); + + await userEvent.click(button); + + await waitFor(() => expect(canvas.getAllByLabelText('complete')).toHaveLength(2)); + }, +}; diff --git a/code/addons/onboarding/src/components/List/List.styled.tsx b/code/addons/onboarding/src/components/List/List.styled.tsx new file mode 100644 index 000000000000..79d91e96dc41 --- /dev/null +++ b/code/addons/onboarding/src/components/List/List.styled.tsx @@ -0,0 +1,9 @@ +import { styled } from '@storybook/theming'; + +export const ListWrapper = styled.ul(() => ({ + display: 'flex', + flexDirection: 'column', + rowGap: 16, + padding: 0, + margin: 0, +})); diff --git a/code/addons/onboarding/src/components/List/List.tsx b/code/addons/onboarding/src/components/List/List.tsx new file mode 100644 index 000000000000..0b9640f7d909 --- /dev/null +++ b/code/addons/onboarding/src/components/List/List.tsx @@ -0,0 +1,10 @@ +import React from 'react'; +import { ListWrapper } from './List.styled'; + +interface ListProps { + children: React.ReactNode; +} + +export const List = ({ children }: ListProps) => { + return {children}; +}; diff --git a/code/addons/onboarding/src/components/List/ListItem/ListItem.styled.tsx b/code/addons/onboarding/src/components/List/ListItem/ListItem.styled.tsx new file mode 100644 index 000000000000..cf37aed81893 --- /dev/null +++ b/code/addons/onboarding/src/components/List/ListItem/ListItem.styled.tsx @@ -0,0 +1,33 @@ +import { styled } from '@storybook/theming'; + +export const ListItemWrapper = styled.li(() => ({ + display: 'flex', + alignItems: 'flex-start', + columnGap: 12, +})); + +export const ListItemContentWrapper = styled.div` + font-family: ${({ theme }) => theme.typography.fonts.base}; + color: ${({ theme }) => theme.color.darker}; + font-size: 13px; + line-height: 18px; + margin-top: 2px; +`; + +export const ListItemIndexWrapper = styled.div<{ isCompleted: boolean }>( + ({ isCompleted, theme }) => ({ + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + border: `1px solid ${!isCompleted ? theme.color.medium : 'transparent'}`, + width: 20, + height: 20, + flexShrink: 0, + borderRadius: '50%', + backgroundColor: isCompleted ? theme.color.green : 'white', + fontFamily: theme.typography.fonts.base, + fontSize: 10, + fontWeight: 600, + color: theme.color.dark, + }) +); diff --git a/code/addons/onboarding/src/components/List/ListItem/ListItem.tsx b/code/addons/onboarding/src/components/List/ListItem/ListItem.tsx new file mode 100644 index 000000000000..e8a9945c0bbe --- /dev/null +++ b/code/addons/onboarding/src/components/List/ListItem/ListItem.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import { ListItemContentWrapper, ListItemIndexWrapper, ListItemWrapper } from './ListItem.styled'; +import { CheckIcon } from '@storybook/icons'; + +interface ListItemProps { + children: React.ReactNode; + index: number; + isCompleted?: boolean; +} + +export const ListItem = ({ children, index, isCompleted }: ListItemProps) => { + return ( + + + {isCompleted ? : index} + + {children} + + ); +}; diff --git a/code/addons/onboarding/src/components/Modal/Modal.stories.tsx b/code/addons/onboarding/src/components/Modal/Modal.stories.tsx new file mode 100644 index 000000000000..51d19c49b4f5 --- /dev/null +++ b/code/addons/onboarding/src/components/Modal/Modal.stories.tsx @@ -0,0 +1,136 @@ +import React, { useState } from 'react'; +import type { Meta, StoryObj } from '@storybook/react'; +import { userEvent, within } from '@storybook/testing-library'; +import { expect } from '@storybook/test'; + +import { Modal } from './Modal'; + +const meta: Meta = { + component: Modal, + decorators: [(storyFn) =>
{storyFn()}
], +}; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = { + args: { + width: undefined, + height: undefined, + }, + render: (props) => { + const [isOpen, setOpen] = useState(false); + + return ( + <> + + {({ Close }) => ( +
+
Hello world!
+ setOpen(false)}>Close +
+ )} +
+ + + ); + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement.parentElement!); + const button = canvas.getByText('Open modal'); + await userEvent.click(button); + await expect(canvas.findByText('Hello world!')).resolves.toBeInTheDocument(); + }, +}; + +export const FixedWidth: Story = { + args: { + ...Default.args, + width: 1024, + }, + render: (props) => { + const [isOpen, setOpen] = useState(false); + + return ( + <> + + {({ Close }) => ( +
+
Hello world!
+ setOpen(false)}>Close +
+ )} +
+ + + ); + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement.parentElement!); + const button = canvas.getByText('Open modal'); + await userEvent.click(button); + await expect(canvas.findByText('Hello world!')).resolves.toBeInTheDocument(); + }, +}; + +export const FixedHeight: Story = { + args: { + ...Default.args, + height: 430, + }, + render: (props) => { + const [isOpen, setOpen] = useState(false); + + return ( + <> + + {({ Close }) => ( +
+
Hello world!
+ setOpen(false)}>Close +
+ )} +
+ + + ); + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement.parentElement!); + const button = canvas.getByText('Open modal'); + await userEvent.click(button); + await expect(canvas.findByText('Hello world!')).resolves.toBeInTheDocument(); + }, +}; + +export const FixedWidthAndHeight: Story = { + args: { + ...Default.args, + width: 1024, + height: 430, + }, + render: (props) => { + const [isOpen, setOpen] = useState(false); + + return ( + <> + + {({ Close }) => ( +
+
Hello world!
+ setOpen(false)}>Close +
+ )} +
+ + + ); + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement.parentElement!); + const button = canvas.getByText('Open modal'); + await userEvent.click(button); + await expect(canvas.findByText('Hello world!')).resolves.toBeInTheDocument(); + }, +}; diff --git a/code/addons/onboarding/src/components/Modal/Modal.styled.tsx b/code/addons/onboarding/src/components/Modal/Modal.styled.tsx new file mode 100644 index 000000000000..42cf6537dd36 --- /dev/null +++ b/code/addons/onboarding/src/components/Modal/Modal.styled.tsx @@ -0,0 +1,50 @@ +import { css, styled } from '@storybook/theming'; +import * as Dialog from '@radix-ui/react-dialog'; +import React from 'react'; + +export const StyledOverlay = styled.div` + background-color: rgba(27, 28, 29, 0.48); + position: fixed; + inset: 0px; + width: 100%; + height: 100%; + z-index: 10; +`; + +export const StyledContent = styled.div<{ + width: number; + height: number; +}>( + ({ width, height }) => css` + background-color: white; + border-radius: 6px; + box-shadow: rgba(14, 18, 22, 0.35) 0px 10px 38px -10px; + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: ${width ?? 740}px; + height: ${height ? `${height}px` : 'auto'}; + max-width: calc(100% - 40px); + max-height: 85vh; + overflow: hidden; + z-index: 11; + + &:focus-visible { + outline: none; + } + ` +); + +export const ContentWrapper = React.forwardRef< + HTMLDivElement, + React.ComponentProps & React.ComponentProps +>(function ContentWrapper({ width, height, children, ...contentProps }, ref) { + return ( + + + {children} + + + ); +}); diff --git a/code/addons/onboarding/src/components/Modal/Modal.tsx b/code/addons/onboarding/src/components/Modal/Modal.tsx new file mode 100644 index 000000000000..4c8bd9bec66d --- /dev/null +++ b/code/addons/onboarding/src/components/Modal/Modal.tsx @@ -0,0 +1,52 @@ +import React from 'react'; +import * as Dialog from '@radix-ui/react-dialog'; +import { ContentWrapper, StyledOverlay } from './Modal.styled'; + +type ContentProps = React.ComponentProps; + +interface ModalProps extends Omit, 'children'> { + width?: number; + height?: number; + children: (props: { + Title: typeof Dialog.Title; + Description: typeof Dialog.Description; + Close: typeof Dialog.Close; + }) => React.ReactNode; + onEscapeKeyDown?: ContentProps['onEscapeKeyDown']; + onInteractOutside?: ContentProps['onInteractOutside']; +} + +export const initial = { opacity: 0 }; +export const animate = { opacity: 1, transition: { duration: 0.3 } }; +export const exit = { opacity: 0, transition: { duration: 0.3 } }; + +export function Modal({ + children, + width, + height, + onEscapeKeyDown, + onInteractOutside = (ev) => ev.preventDefault(), + ...rootProps +}: ModalProps) { + return ( + + + + + + + {children({ + Title: Dialog.Title, + Description: Dialog.Description, + Close: Dialog.Close, + })} + + + + ); +} diff --git a/code/addons/onboarding/src/components/PulsatingEffect/PulsatingEffect.stories.tsx b/code/addons/onboarding/src/components/PulsatingEffect/PulsatingEffect.stories.tsx new file mode 100644 index 000000000000..67b31843dc45 --- /dev/null +++ b/code/addons/onboarding/src/components/PulsatingEffect/PulsatingEffect.stories.tsx @@ -0,0 +1,44 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { PulsatingEffect } from './PulsatingEffect'; +import React from 'react'; +import { within } from '@storybook/testing-library'; +import { expect } from '@storybook/test'; + +const meta: Meta = { + component: PulsatingEffect, + parameters: { + layout: 'centered', + chromatic: { + disableSnapshot: true, + }, + }, +}; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = { + render: () => ( + <> + + + + ), + play: async ({ canvasElement }) => { + const canvas = within(canvasElement.parentElement!); + const button = canvas.getByRole('button'); + await expect(button).toHaveStyle( + 'animation: 3s ease-in-out 0s infinite normal none running pulsate' + ); + }, +}; diff --git a/code/addons/onboarding/src/components/PulsatingEffect/PulsatingEffect.tsx b/code/addons/onboarding/src/components/PulsatingEffect/PulsatingEffect.tsx new file mode 100644 index 000000000000..23cb853d7856 --- /dev/null +++ b/code/addons/onboarding/src/components/PulsatingEffect/PulsatingEffect.tsx @@ -0,0 +1,49 @@ +import { useEffect } from 'react'; + +export function PulsatingEffect({ + targetSelector, +}: { + targetSelector: string; +}): JSX.Element | null { + useEffect(() => { + const element = document.querySelector(targetSelector); + + if (element) { + element.style.animation = 'pulsate 3s infinite'; + element.style.transformOrigin = 'center'; + element.style.animationTimingFunction = 'ease-in-out'; + + const keyframes = ` + @keyframes pulsate { + 0% { + box-shadow: 0 0 0 0 rgba(2, 156, 253, 0.7), 0 0 0 0 rgba(2, 156, 253, 0.4); + } + 50% { + box-shadow: 0 0 0 20px rgba(2, 156, 253, 0), 0 0 0 40px rgba(2, 156, 253, 0); + } + 100% { + box-shadow: 0 0 0 0 rgba(2, 156, 253, 0), 0 0 0 0 rgba(2, 156, 253, 0); + } + } + `; + const style = document.createElement('style'); + style.id = 'sb-onboarding-pulsating-effect'; + style.innerHTML = keyframes; + document.head.appendChild(style); + } + + return () => { + const styleElement = document.querySelector('#sb-onboarding-pulsating-effect'); + + if (styleElement) { + styleElement.remove(); + } + + if (element) { + element.style.animation = 'auto'; + } + }; + }, [targetSelector]); + + return null; +} diff --git a/code/addons/onboarding/src/components/SyntaxHighlighter/Snippet/Snippet.styled.tsx b/code/addons/onboarding/src/components/SyntaxHighlighter/Snippet/Snippet.styled.tsx new file mode 100644 index 000000000000..57a22c42d5d8 --- /dev/null +++ b/code/addons/onboarding/src/components/SyntaxHighlighter/Snippet/Snippet.styled.tsx @@ -0,0 +1,9 @@ +import { styled } from '@storybook/theming'; +import { motion } from 'framer-motion'; + +export const SnippetWrapper = styled(motion.div)` + position: relative; + padding-top: 12px; + padding-bottom: 12px; + min-height: 57px; +`; diff --git a/code/addons/onboarding/src/components/SyntaxHighlighter/Snippet/Snippet.tsx b/code/addons/onboarding/src/components/SyntaxHighlighter/Snippet/Snippet.tsx new file mode 100644 index 000000000000..82f5c94bbeea --- /dev/null +++ b/code/addons/onboarding/src/components/SyntaxHighlighter/Snippet/Snippet.tsx @@ -0,0 +1,75 @@ +import { motion } from 'framer-motion'; +import { Fragment, forwardRef } from 'react'; +import { SnippetWrapper } from './Snippet.styled'; +import React from 'react'; +import { SyntaxHighlighter as StorybookSyntaxHighlighter } from '@storybook/components'; + +interface Props { + content: { snippet: string; toggle?: boolean }[]; + active: boolean; + open?: boolean; +} + +const wrapperVariants = { + default: { + filter: 'grayscale(1)', + opacity: 0.5, + }, + active: { + filter: 'grayscale(0)', + opacity: 1, + }, +}; + +export const Snippet = forwardRef(function Snippet( + { active, content, open }, + ref +) { + const customStyle = { + fontSize: '0.8125rem', + lineHeight: '1.1875rem', + }; + + return ( + + {content.map(({ toggle, snippet }, i) => ( + + {toggle === undefined && ( + + {snippet} + + )} + + {toggle && !open && ( + + {` // ...`} + + )} + + {toggle && open && ( + + + {snippet} + + + )} + + ))} + + ); +}); diff --git a/code/addons/onboarding/src/components/SyntaxHighlighter/SyntaxHighlighter.stories.tsx b/code/addons/onboarding/src/components/SyntaxHighlighter/SyntaxHighlighter.stories.tsx new file mode 100644 index 000000000000..5d29d4a42c22 --- /dev/null +++ b/code/addons/onboarding/src/components/SyntaxHighlighter/SyntaxHighlighter.stories.tsx @@ -0,0 +1,84 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { SyntaxHighlighter } from './SyntaxHighlighter'; +import React from 'react'; +import type { CodeSnippets } from '../../features/WriteStoriesModal/code/types'; + +const meta: Meta = { + component: SyntaxHighlighter, + parameters: { + chromatic: { delay: 300 }, + }, +}; + +export default meta; + +type Story = StoryObj; + +const data: CodeSnippets['code'] = [ + [ + { + snippet: `import type { Meta, StoryObj } from '@storybook/react'; + +import { Button } from './Button';`, + }, + ], + [ + { + snippet: `const meta: Meta = { + title: 'Example/Button', + component: Button, + // ... +}; + +export default meta;`, + }, + ], + [ + { snippet: `export const Primary: Story = {` }, + { + snippet: `args: { + primary: true, + label: 'Click', + background: 'red' + }`, + toggle: true, + }, + { snippet: `};` }, + ], + [ + { + snippet: `// Copy the code below + +export const Warning: Story = { + args: { + primary: true, + label: 'Delete now', + backgroundColor: 'red', + } +};`, + }, + ], +]; + +export const Default: Story = { + render: (args) => { + const [activeStep, setActiveStep] = React.useState(0); + + return ( +
+ + + + +
+ ); + }, + args: { + data: data, + activeStep: 0, + width: 480, + filename: 'Button.stories.tsx', + }, +}; diff --git a/code/addons/onboarding/src/components/SyntaxHighlighter/SyntaxHighlighter.styled.tsx b/code/addons/onboarding/src/components/SyntaxHighlighter/SyntaxHighlighter.styled.tsx new file mode 100644 index 000000000000..29beed376ed0 --- /dev/null +++ b/code/addons/onboarding/src/components/SyntaxHighlighter/SyntaxHighlighter.styled.tsx @@ -0,0 +1,54 @@ +import { styled } from '@storybook/theming'; +import { motion } from 'framer-motion'; + +export const Code = styled(motion.div)` + position: relative; + z-index: 2; +`; + +export const SnippetWrapperFirst = styled(motion.div)` + position: relative; + padding-top: 10px; + padding-bottom: 10px; +`; + +export const SnippetWrapper = styled(motion.div)` + position: relative; + padding-top: 12px; + padding-bottom: 12px; +`; + +export const Container = styled.div<{ width: number }>` + position: relative; + box-sizing: border-box; + background: #171c23; + width: ${({ width }) => width}px; + height: 100%; + overflow: hidden; + padding-left: 15px; + padding-right: 15px; + padding-top: 4px; + border-left: ${({ theme }) => (theme.base === 'dark' ? 1 : 0)}px solid #fff2; + border-bottom: ${({ theme }) => (theme.base === 'dark' ? 1 : 0)}px solid #fff2; + border-top: ${({ theme }) => (theme.base === 'dark' ? 1 : 0)}px solid #fff2; + border-radius: 6px 0 0 6px; + overflow: hidden; + + && { + pre { + background: transparent !important; + margin: 0 !important; + padding: 0 !important; + } + } +`; + +export const Backdrop = styled(motion.div)` + background: #143046; + position: absolute; + z-index: 1; + left: 0; + top: 44px; + width: 100%; + height: 81px; +`; diff --git a/code/addons/onboarding/src/components/SyntaxHighlighter/SyntaxHighlighter.tsx b/code/addons/onboarding/src/components/SyntaxHighlighter/SyntaxHighlighter.tsx new file mode 100644 index 000000000000..a1038465b926 --- /dev/null +++ b/code/addons/onboarding/src/components/SyntaxHighlighter/SyntaxHighlighter.tsx @@ -0,0 +1,117 @@ +import React, { createRef, useCallback, useLayoutEffect, useMemo, useState } from 'react'; +import { Backdrop, Code, Container, SnippetWrapperFirst } from './SyntaxHighlighter.styled'; +import { Snippet } from './Snippet/Snippet'; +import { ThemeProvider, ensure, themes } from '@storybook/theming'; +import { SyntaxHighlighter as StorybookSyntaxHighlighter } from '@storybook/components'; + +type SyntaxHighlighterProps = { + data: { snippet: string; toggle?: boolean }[][]; + activeStep: number; + width: number; + filename: string; +}; + +type StepsProps = { + yPos: number; + backdropHeight: number; + index: number; + open: boolean; +}; + +export const SyntaxHighlighter = ({ + activeStep, + data, + width, + filename, +}: SyntaxHighlighterProps) => { + const [steps, setSteps] = useState([]); + + const refs = useMemo(() => data.map(() => createRef()), [data]); + + const getYPos = (idx: number) => { + let yPos = 0; + for (let i = 0; i < idx; i++) { + yPos -= refs[i].current!.getBoundingClientRect().height; + } + return yPos; + }; + + const setNewSteps = useCallback(() => { + const newSteps = data.flatMap((content, i) => { + const backdropHeight = refs[i].current!.getBoundingClientRect().height; + const finalSteps = [ + { + yPos: getYPos(i), + backdropHeight, + index: i, + open: false, + }, + ]; + + if (content.length > 1) { + finalSteps.push({ + yPos: getYPos(i), + backdropHeight, + index: i, + open: true, + }); + } + + return finalSteps; + }); + + setSteps(newSteps); + }, [data]); + + useLayoutEffect(() => { + // Call setNewSteps every time height of the refs elements changes + const resizeObserver = new ResizeObserver(() => { + setNewSteps(); + }); + + refs.forEach((ref) => { + resizeObserver.observe(ref.current!); + }); + + return () => { + resizeObserver.disconnect(); + }; + }, []); + + const customStyle = { + fontSize: '0.8125rem', + lineHeight: '1.1875rem', + }; + + return ( + + + + + + {'// ' + filename} + + + {data.map((content, idx: number) => ( + idx ? true : steps[activeStep]?.open ?? false} + content={content} + /> + ))} + + + + + ); +}; diff --git a/code/addons/onboarding/src/constants.ts b/code/addons/onboarding/src/constants.ts new file mode 100644 index 000000000000..f81e55b4cf93 --- /dev/null +++ b/code/addons/onboarding/src/constants.ts @@ -0,0 +1 @@ +export const STORYBOOK_ADDON_ONBOARDING_CHANNEL = 'STORYBOOK_ADDON_ONBOARDING_CHANNEL'; diff --git a/code/addons/onboarding/src/features/GuidedTour/GuidedTour.styled.tsx b/code/addons/onboarding/src/features/GuidedTour/GuidedTour.styled.tsx new file mode 100644 index 000000000000..5f666ab74aae --- /dev/null +++ b/code/addons/onboarding/src/features/GuidedTour/GuidedTour.styled.tsx @@ -0,0 +1,20 @@ +import { type Theme } from '@storybook/theming'; + +export const getStyles = (theme: Theme) => ({ + border: 0, + borderRadius: '0.25rem', + cursor: 'pointer', + display: 'inline-flex', + alignItems: 'center', + justifyContent: 'center', + padding: '0 0.75rem', + background: theme.color.secondary, + color: '#FFF', + height: 32, + fontSize: '0.8125rem', + fontWeight: 700, + fontFamily: theme.typography.fonts.base, + transition: 'all 0.16s ease-in-out', + textDecoration: 'none', + outline: 'none', +}); diff --git a/code/addons/onboarding/src/features/GuidedTour/GuidedTour.tsx b/code/addons/onboarding/src/features/GuidedTour/GuidedTour.tsx new file mode 100644 index 000000000000..c4fdcc240a2e --- /dev/null +++ b/code/addons/onboarding/src/features/GuidedTour/GuidedTour.tsx @@ -0,0 +1,207 @@ +import type { ComponentProps } from 'react'; +import React, { useEffect, useMemo, useState } from 'react'; +import type { CallBackProps } from 'react-joyride'; +import Joyride, { STATUS } from 'react-joyride'; +import type { API } from '@storybook/manager-api'; +import { UPDATE_STORY_ARGS } from '@storybook/core-events'; +import { useTheme } from '@storybook/theming'; + +import { PulsatingEffect } from '../../components/PulsatingEffect/PulsatingEffect'; +import { Confetti } from '../../components/Confetti/Confetti'; +import { Tooltip } from './Tooltip'; +import { SpanHighlight } from '../WriteStoriesModal/WriteStoriesModal.styled'; +import type { CodeSnippets } from '../WriteStoriesModal/code/types'; + +type GuidedTourStep = ComponentProps['step']; + +export function GuidedTour({ + api, + isFinalStep, + onFirstTourDone, + onLastTourDone, + codeSnippets, +}: { + api: API; + isFinalStep?: boolean; + codeSnippets?: CodeSnippets; + onFirstTourDone: () => void; + onLastTourDone: () => void; +}) { + const [stepIndex, setStepIndex] = useState(); + const theme = useTheme(); + + useEffect(() => { + api.once(UPDATE_STORY_ARGS, () => { + setStepIndex(3); + }); + }, []); + + const storyPlaygroundElement = useMemo(() => { + return (document.querySelector('#root div[role=main]') || + document.querySelector('#storybook-panel-root')) as HTMLElement; + }, []); + + const steps: GuidedTourStep[] = isFinalStep + ? [ + { + target: '#example-button--warning', + title: 'Congratulations!', + content: ( + <> + You just created your first story. Continue setting up your project to write stories + for your own components. + + ), + placement: 'right', + disableOverlay: true, + disableBeacon: true, + floaterProps: { + disableAnimation: true, + }, + onNextButtonClick() { + onLastTourDone(); + }, + }, + ] + : [ + { + target: '#storybook-explorer-tree > div', + title: 'Storybook is built from stories', + content: ( + <> + Storybook stories represent the key states of each of your components. +
+
+ {codeSnippets?.filename && ( + <> + We automatically added four stories for this Button component in this example + file: +
+ {codeSnippets.filename} + + )} + + ), + placement: 'right', + disableBeacon: true, + styles: { + spotlight: { + transform: 'translateY(30px)', + }, + }, + floaterProps: { + disableAnimation: true, + }, + }, + { + target: '#storybook-preview-iframe', + title: 'Storybook previews are interactive', + content: + 'Whenever you modify code or stories, Storybook automatically updates how it previews your components.', + placement: 'bottom', + styles: { + spotlight: { + borderRadius: 0, + }, + }, + }, + { + target: storyPlaygroundElement, + title: 'Interactive story playground', + content: ( + <> + See how a story renders with different data and state without touching code. +
+
+ Try it out by pressing this button. + + + ), + placement: 'right', + spotlightClicks: true, + floaterProps: { + target: '#control-primary', + options: { + preventOverflow: { + boundariesElement: 'window', + }, + }, + }, + hideNextButton: true, + }, + { + target: '#control-primary', + title: 'Congratulations!', + content: ( + <> + You learned how to control your stories interactively. Now let's explore how to write + your first story. + + + ), + placement: 'right', + floaterProps: { + options: { + preventOverflow: { + boundariesElement: 'window', + }, + }, + }, + disableOverlay: true, + }, + ]; + + return ( + { + if (!isFinalStep && data.status === STATUS.FINISHED) { + onFirstTourDone(); + } + }} + floaterProps={{ + options: { + offset: { + offset: '0, 6', + }, + }, + styles: { + floater: { + padding: 0, + paddingLeft: 8, + paddingTop: 8, + filter: + theme.base === 'light' + ? 'drop-shadow(0px 5px 5px rgba(0,0,0,0.05)) drop-shadow(0 1px 3px rgba(0,0,0,0.1))' + : 'drop-shadow(#fff5 0px 0px 0.5px) drop-shadow(#fff5 0px 0px 0.5px)', + }, + }, + }} + tooltipComponent={Tooltip} + styles={{ + overlay: { + mixBlendMode: 'unset', + backgroundColor: 'none', + }, + spotlight: { + backgroundColor: 'none', + border: `solid 2px ${theme.color.secondary}`, + boxShadow: '0px 0px 0px 9999px rgba(0,0,0,0.4)', + }, + options: { + zIndex: 10000, + primaryColor: theme.color.secondary, + arrowColor: theme.base === 'dark' ? '#292A2C' : theme.color.lightest, + }, + }} + /> + ); +} diff --git a/code/addons/onboarding/src/features/GuidedTour/Tooltip.tsx b/code/addons/onboarding/src/features/GuidedTour/Tooltip.tsx new file mode 100644 index 000000000000..c58fcbe1ad7f --- /dev/null +++ b/code/addons/onboarding/src/features/GuidedTour/Tooltip.tsx @@ -0,0 +1,91 @@ +import type { FC } from 'react'; +import React from 'react'; +import { styled } from '@storybook/theming'; +import type { Step, TooltipRenderProps } from 'react-joyride'; +import { Button } from '../../components/Button/Button'; + +const TooltipBody = styled.div` + background: ${({ theme }) => { + return theme.base === 'dark' ? '#292A2C' : theme.color.lightest; + }}; + width: 260px; + padding: 15px; + border-radius: 5px; +`; + +const Wrapper = styled.div` + display: flex; + flex-direction: column; + align-items: flex-start; +`; + +const TooltipTitle = styled.div` + font-size: 13px; + line-height: 18px; + font-weight: 700; + color: ${({ theme }) => theme.color.defaultText}; +`; + +const TooltipContent = styled.p` + font-size: 13px; + line-height: 18px; + text-align: start; + color: ${({ theme }) => theme.color.defaultText}; + margin: 0; + margin-top: 5px; +`; + +const TooltipFooter = styled.div` + display: flex; + justify-content: flex-end; + margin-top: 15px; +`; + +type TooltipProps = { + step: Partial< + Pick< + // this only seems to happen during the check task, nos in vsCode.. + // this seems to be 'any' in vsCode because of it? + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore (hide property 'input' circularly references itself in mapped type) + Step, + | 'title' + | 'content' + | 'target' + | 'placement' + | 'disableOverlay' + | 'disableBeacon' + | 'floaterProps' + | 'spotlightClicks' + | 'styles' + > & { + hideNextButton: boolean; + onNextButtonClick: () => void; + } + >; + primaryProps: TooltipRenderProps['primaryProps']; + tooltipProps: TooltipRenderProps['tooltipProps']; +}; + +export const Tooltip: FC = ({ step, primaryProps, tooltipProps }) => { + return ( + + + {step.title && {step.title}} + {step.content} + + {!step.hideNextButton && ( + + + + )} + + ); +}; diff --git a/code/addons/onboarding/src/features/WelcomeModal/StorybookLogo.tsx b/code/addons/onboarding/src/features/WelcomeModal/StorybookLogo.tsx new file mode 100644 index 000000000000..bcfbba836ab9 --- /dev/null +++ b/code/addons/onboarding/src/features/WelcomeModal/StorybookLogo.tsx @@ -0,0 +1,26 @@ +import React from 'react'; + +export function StorybookLogo() { + return ( + + + + + + + + + + + + + ); +} diff --git a/code/addons/onboarding/src/features/WelcomeModal/WelcomeModal.stories.tsx b/code/addons/onboarding/src/features/WelcomeModal/WelcomeModal.stories.tsx new file mode 100644 index 000000000000..ecf153295ee6 --- /dev/null +++ b/code/addons/onboarding/src/features/WelcomeModal/WelcomeModal.stories.tsx @@ -0,0 +1,15 @@ +import React from 'react'; +import type { Meta, StoryObj } from '@storybook/react'; + +import { WelcomeModal } from './WelcomeModal'; + +const meta: Meta = { + component: WelcomeModal, + decorators: [(storyFn) =>
{storyFn()}
], +}; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = {}; diff --git a/code/addons/onboarding/src/features/WelcomeModal/WelcomeModal.styled.tsx b/code/addons/onboarding/src/features/WelcomeModal/WelcomeModal.styled.tsx new file mode 100644 index 000000000000..503c97e102ef --- /dev/null +++ b/code/addons/onboarding/src/features/WelcomeModal/WelcomeModal.styled.tsx @@ -0,0 +1,146 @@ +import { ArrowRightIcon } from '@storybook/icons'; +import { keyframes, styled } from '@storybook/theming'; + +export const ModalContentWrapper = styled.div` + border-radius: 5px; + display: flex; + flex-direction: column; + align-items: center; + height: 100%; + justify-content: space-between; +`; + +export const TopContent = styled.div` + display: flex; + flex: 1; + flex-direction: column; + align-items: center; + justify-content: center; +`; + +export const Title = styled.h1` + margin: 0; + margin-top: 20px; + margin-bottom: 5px; + color: ${({ theme }) => theme.color.darkest}; + font-weight: ${({ theme }) => theme.typography.weight.bold}; + font-size: ${({ theme }) => theme.typography.size.m1}px; + line-height: ${({ theme }) => theme.typography.size.m3}px; +`; + +export const Description = styled.p` + margin: 0; + margin-bottom: 20px; + max-width: 320px; + text-align: center; + font-size: ${({ theme }) => theme.typography.size.s2}px; + font-weight: ${({ theme }) => theme.typography.weight.regular}; + line-height: ${({ theme }) => theme.typography.size.m1}px; + color: ${({ theme }) => theme.color.darker}; +`; + +export const SkipButton = styled.button` + all: unset; + cursor: pointer; + font-size: 13px; + color: #798186; + padding-bottom: 20px; + + &:focus-visible { + outline: auto; + } +`; + +export const StyledIcon = styled(ArrowRightIcon)` + margin-left: 2px; + height: 10px; +`; + +export const Background = styled.div` + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: -1; + overflow: hidden; +`; + +export const circle1Anim = keyframes` + 0% { transform: translate(0px, 0px) } + 50% { transform: translate(-200px, 0px) } + 100% { transform: translate(0px, 0px) } +`; + +export const Circle1 = styled.div` + position: absolute; + width: 1200px; + height: 1200px; + left: -200px; + top: -900px; + background: radial-gradient( + circle at center, + rgba(253, 255, 147, 1) 0%, + rgba(253, 255, 147, 0) 70% + ); + animation: ${circle1Anim} 4s linear infinite; + animation-timing-function: ease-in-out; + z-index: 3; +`; + +export const circle2Anim = keyframes` + 0% { transform: translate(0px, 0px) } + 50% { transform: translate(400px, 0px) } + 100% { transform: translate(0px, 0px) } +`; + +export const Circle2 = styled.div` + position: absolute; + width: 1200px; + height: 1200px; + left: -600px; + top: -840px; + background: radial-gradient( + circle at center, + rgba(255, 119, 119, 1) 0%, + rgba(255, 119, 119, 0) 70% + ); + animation: ${circle2Anim} 6s linear infinite; + animation-timing-function: ease-in-out; + z-index: 2; +`; + +export const circle3Anim = keyframes` + 0% { transform: translate(600px, -40px) } + 50% { transform: translate(600px, -200px) } + 100% { transform: translate(600px, -40px) } +`; + +export const Circle3 = styled.div` + position: absolute; + width: 1200px; + height: 1200px; + left: -600px; + top: -840px; + background: radial-gradient( + circle at center, + rgba(119, 255, 247, 0.8) 0%, + rgba(119, 255, 247, 0) 70% + ); + animation: ${circle3Anim} 4s linear infinite; + animation-timing-function: ease-in-out; + z-index: 4; +`; + +export const StyledTitle = styled.h2` + color: #000; + font-weight: 700; + font-size: 20px; + line-height: 20px; +`; +export const StyledDescription = styled.p` + font-size: 14px; + font-weight: 400; + line-height: 20px; + color: #454e54; +`; diff --git a/code/addons/onboarding/src/features/WelcomeModal/WelcomeModal.tsx b/code/addons/onboarding/src/features/WelcomeModal/WelcomeModal.tsx new file mode 100644 index 000000000000..9459529970e9 --- /dev/null +++ b/code/addons/onboarding/src/features/WelcomeModal/WelcomeModal.tsx @@ -0,0 +1,56 @@ +import type { FC } from 'react'; +import React from 'react'; + +import { Button } from '../../components/Button/Button'; +import { Modal } from '../../components/Modal/Modal'; +import { StorybookLogo } from './StorybookLogo'; +import { + ModalContentWrapper, + SkipButton, + StyledIcon, + Title, + Description, + Background, + Circle1, + Circle2, + Circle3, + TopContent, +} from './WelcomeModal.styled'; + +interface WelcomeModalProps { + onProceed: () => void; + skipOnboarding: () => void; +} + +export const WelcomeModal: FC = ({ onProceed, skipOnboarding }) => { + return ( +
+ + {({ Close }) => ( + + + + Welcome to Storybook + + Storybook helps you develop UI components faster. Learn the basics in a few simple + steps. + + + + + Skip tour + + + + + + + + + )} + +
+ ); +}; diff --git a/code/addons/onboarding/src/features/WriteStoriesModal/WriteStoriesModal.stories.tsx b/code/addons/onboarding/src/features/WriteStoriesModal/WriteStoriesModal.stories.tsx new file mode 100644 index 000000000000..d2284dbd913f --- /dev/null +++ b/code/addons/onboarding/src/features/WriteStoriesModal/WriteStoriesModal.stories.tsx @@ -0,0 +1,83 @@ +import React from 'react'; +import type { Meta, StoryObj } from '@storybook/react'; + +import { waitFor, within } from '@storybook/testing-library'; +import { expect, fn } from '@storybook/test'; +import { STORY_INDEX_INVALIDATED, STORY_RENDERED } from '@storybook/core-events'; +import { WriteStoriesModal } from './WriteStoriesModal'; +import typescriptSnippet from './code/typescript'; + +const getData = fn(); + +const meta: Meta = { + component: WriteStoriesModal, + args: { + codeSnippets: typescriptSnippet, + // @ts-expect-error (bad) + api: { + getData, + }, + addonsStore: { + // @ts-expect-error (bad) + getChannel: () => ({ + once: (type: string, cb: () => void) => { + if (type === STORY_RENDERED) { + cb(); + } + }, + on: (type: string, cb: () => void) => { + if (type === STORY_INDEX_INVALIDATED) { + storyIndexInvalidatedCb = cb; + } + }, + off: () => {}, + }), + }, + }, + decorators: [ + (storyFn, context) => { + (context.args.api.getData as typeof getData) + // do not respond to the first call, this would only return the data correctly if the story already exists + // which is not the case in this story, it only makes sense in the real scenario + .mockReturnValueOnce(null) + .mockReturnValueOnce({ some: 'data' }); + return
{storyFn()}
; + }, + ], +}; + +export default meta; + +type Story = StoryObj; + +let storyIndexInvalidatedCb: () => void; + +export const Default: Story = {}; + +export const DefaultPlayed: Story = { + args: { + ...Default.args, + }, + play: async ({ canvasElement, step }) => { + const canvas = within(canvasElement.parentElement!); + const importsText = await canvas.findByText('Imports'); + await step('Wait for modal to be visible', async () => { + const modal = await canvas.findByRole('dialog'); + await waitFor(async () => expect(modal).toBeVisible()); + }); + await expect(importsText).toBeVisible(); + await canvas.getByRole('button', { name: /Next/i }).click(); + const metaText = await canvas.findAllByText('Meta'); + await expect(metaText?.[0]).toBeVisible(); + await canvas.getByRole('button', { name: /Next/i }).click(); + const storyText = await canvas.findAllByText('Story'); + await expect(storyText?.[0]).toBeVisible(); + await canvas.getByRole('button', { name: /Next/i }).click(); + const argsText = await canvas.findAllByText('Args'); + await expect(argsText?.[0]).toBeVisible(); + await canvas.getByRole('button', { name: /Next/i }).click(); + (await canvas.findByRole('button', { name: /Copy code/i })).click(); + storyIndexInvalidatedCb(); + await waitFor(() => expect(canvas.getAllByLabelText('complete')).toHaveLength(3)); + }, +}; diff --git a/code/addons/onboarding/src/features/WriteStoriesModal/WriteStoriesModal.styled.tsx b/code/addons/onboarding/src/features/WriteStoriesModal/WriteStoriesModal.styled.tsx new file mode 100644 index 000000000000..b47dba7c7c39 --- /dev/null +++ b/code/addons/onboarding/src/features/WriteStoriesModal/WriteStoriesModal.styled.tsx @@ -0,0 +1,170 @@ +import { keyframes, styled } from '@storybook/theming'; + +export const ModalContent = styled.div` + display: flex; + flex-direction: row; + height: 100%; + max-height: 85vh; + + &:focus-visible { + outline: none; + } +`; + +export const Main = styled.div` + position: relative; + flex: 1; + display: flex; + flex-direction: column; + font-family: ${({ theme }) => theme.typography.fonts.base}; +`; + +export const Header = styled.div` + box-sizing: border-box; + display: flex; + justify-content: space-between; + align-items: center; + padding: 0 15px; + border-bottom: 1px solid rgba(0, 0, 0, 0.1); + height: 44px; +`; + +export const ModalTitle = styled.div` + display: flex; + align-items: center; + gap: 5px; + font-size: 13px; + line-height: 18px; + font-weight: bold; + color: ${({ theme }) => theme.color.darkest}; +`; + +export const Content = styled.div` + font-size: 13px; + line-height: 18px; + padding: 15px; + flex: 1; + display: flex; + flex-direction: column; + align-items: flex-end; + justify-content: space-between; + color: ${({ theme }) => theme.color.darker}; + + h3 { + font-size: 13px; + line-height: 18px; + font-weight: bold; + padding: 0; + margin: 0; + } +`; + +export const SpanHighlight = styled.span` + display: inline-flex; + border-radius: 3px; + padding: 0 5px; + margin-bottom: -2px; + opacity: 0.8; + font-family: ${({ theme }) => theme.typography.fonts.mono}; + font-size: 11px; + border: 1px solid #ecf4f9; + color: ${({ theme }) => theme.color.darkest}; + background-color: #f7fafc; + box-sizing: border-box; + line-height: 17px; +`; + +export const Image = styled.img` + max-width: 100%; + margin-top: 1em; +`; + +export const Background = styled.div` + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: -1; + overflow: hidden; +`; + +export const circle1Anim = keyframes` + 0% { transform: translate(0px, 0px) } + 50% { transform: translate(120px, 0px) } + 100% { transform: translate(0px, 0px) } +`; + +export const Circle1 = styled.div` + position: absolute; + width: 350px; + height: 350px; + left: -160px; + top: -260px; + background: radial-gradient( + circle at center, + rgba(255, 119, 119, 1) 0%, + rgba(255, 119, 119, 0) 70% + ); + animation: ${circle1Anim} 8s linear infinite; + animation-timing-function: ease-in-out; + z-index: 2; +`; + +export const circle2Anim = keyframes` + 0% { transform: translate(0px, 0px) } + 33% { transform: translate(-64px, 0px) } + 66% { transform: translate(120px, 0px) } + 100% { transform: translate(0px, 0px) } +`; + +export const Circle2 = styled.div` + position: absolute; + width: 350px; + height: 350px; + left: -54px; + top: -250px; + background: radial-gradient( + circle at center, + rgba(253, 255, 147, 1) 0%, + rgba(253, 255, 147, 0) 70% + ); + animation: ${circle2Anim} 12s linear infinite; + animation-timing-function: ease-in-out; + z-index: 3; +`; + +export const circle3Anim = keyframes` + 0% { transform: translate(0px, 0px) } + 50% { transform: translate(-120px, 0px) } + 100% { transform: translate(0px, 0px) } +`; + +export const Circle3 = styled.div` + position: absolute; + width: 350px; + height: 350px; + left: 150px; + top: -220px; + background: radial-gradient( + circle at center, + rgba(119, 255, 247, 0.8) 0%, + rgba(119, 255, 247, 0) 70% + ); + animation: ${circle3Anim} 4s linear infinite; + animation-timing-function: ease-in-out; + z-index: 4; +`; + +export const ButtonsWrapper = styled.div` + box-sizing: border-box; + display: flex; + justify-content: space-between; + align-items: center; + width: 100%; + margin-top: 4px; +`; + +export const Step2Text = styled.div` + margin-bottom: 4px; +`; diff --git a/code/addons/onboarding/src/features/WriteStoriesModal/WriteStoriesModal.tsx b/code/addons/onboarding/src/features/WriteStoriesModal/WriteStoriesModal.tsx new file mode 100644 index 000000000000..b456b02240c9 --- /dev/null +++ b/code/addons/onboarding/src/features/WriteStoriesModal/WriteStoriesModal.tsx @@ -0,0 +1,291 @@ +import React, { useCallback, useState, type FC } from 'react'; +import { Button } from '../../components/Button/Button'; +import { Modal } from '../../components/Modal/Modal'; +import useMeasure from 'react-use-measure'; +import { + Background, + ButtonsWrapper, + Circle1, + Circle2, + Circle3, + Content, + Header, + Image, + Main, + ModalContent, + ModalTitle, + SpanHighlight, + Step2Text, +} from './WriteStoriesModal.styled'; +import { SyntaxHighlighter } from '../../components/SyntaxHighlighter/SyntaxHighlighter'; +import { List } from '../../components/List/List'; +import { ListItem } from '../../components/List/ListItem/ListItem'; +import { useGetButtonPath } from './hooks/useGetButtonPath'; +import { useGetWarningButtonStatus } from './hooks/useGetWarningButtonStatus'; +import { useGetBackdropBoundary } from './hooks/useGetBackdropBoundary'; +import titleSidebarImg from './assets/01-title-sidebar.png'; +import storyNameSidebarImg from './assets/02-story-name-sidebar.png'; +import argsImg from './assets/03-args.png'; +import type { API, AddonStore } from '@storybook/manager-api'; +import { STORYBOOK_ADDON_ONBOARDING_CHANNEL } from '../../constants'; +import { useTheme } from '@storybook/theming'; +import type { CodeSnippets } from './code/types'; +import { BookmarkHollowIcon, CrossIcon } from '@storybook/icons'; + +// TODO: Add warning if backdropBoundary && !warningButtonStatus?.data is not true. +// backdropBoundary && !warningButtonStatus?.data + +interface WriteStoriesModalProps { + onFinish: () => void; + api: API; + addonsStore: AddonStore; + codeSnippets: CodeSnippets; + skipOnboarding: () => void; +} + +export const WriteStoriesModal: FC = ({ + onFinish, + api, + addonsStore, + skipOnboarding, + codeSnippets, +}) => { + const [step, setStep] = useState<'imports' | 'meta' | 'story' | 'args' | 'customStory'>( + 'imports' + ); + const theme = useTheme(); + + const stepIndex = { + imports: 0, + meta: 1, + story: 2, + args: 3, + customStory: 4, + }; + + const [isWarningStoryCopied, setWarningStoryCopied] = useState(false); + + const [clipboardButtonRef, clipboardButtonBounds] = useMeasure(); + + const buttonPath = useGetButtonPath(); + const warningButtonStatus = useGetWarningButtonStatus(step === 'customStory', api, addonsStore); + const backdropBoundary = useGetBackdropBoundary( + 'syntax-highlighter-backdrop', + step === 'customStory' + ); + + const isJavascript = codeSnippets?.language === 'javascript'; + + const copyWarningStory = () => { + const warningContent = codeSnippets.code[3][0].snippet; + navigator.clipboard.writeText(warningContent.replace('// Copy the code below', '')); + setWarningStoryCopied(true); + }; + + const onModalClose = useCallback(() => { + api.emit(STORYBOOK_ADDON_ONBOARDING_CHANNEL, { + step: 'X:SkippedOnboarding', + where: `HowToWriteAStoryModal:${step}`, + type: 'telemetry', + }); + }, [api, step]); + + return ( + + {({ Title, Description, Close }) => ( + + {codeSnippets ? ( + + ) : null} + {step === 'customStory' && backdropBoundary && !warningButtonStatus?.data && ( + + )} +
+
+ + <ModalTitle> + <BookmarkHollowIcon width={13} /> + <span>How to write a story</span> + </ModalTitle> + + + + +
+ + + {step === 'imports' && ( + <> +
+

Imports

+ {isJavascript ? ( +

Import a component. In this case, the Button component.

+ ) : ( + <> +

+ First, import Meta and{' '} + StoryObj for type safety and + autocompletion in TypeScript stories. +

+

Next, import a component. In this case, the Button component.

+ + )} +
+ + + )} + {step === 'meta' && ( + <> +
+

Meta

+

+ The default export, Meta, contains metadata about this component's stories. + The title field (optional) controls where stories appear in the sidebar. +

+ Title property pointing to Storybook's sidebar +
+ + + + + + )} + {step === 'story' && ( + <> +
+

Story

+

+ Each named export is a story. Its contents specify how the story is rendered + in addition to other configuration options. +

+ Story export pointing to the sidebar entry of the story +
+ + + + + + )} + {step === 'args' && ( + <> +
+

Args

+

+ Args are inputs that are passed to the component, which Storybook uses to + render the component in different states. In React, args = props. They also + specify the initial control values for the story. +

+ Args mapped to their controls in Storybook +
+ + + + + + )} + {step === 'customStory' && + (!warningButtonStatus?.error ? ( + <> +
+

Create your first story

+

+ Now it's your turn. See how easy it is to create your first story by + following these steps below. +

+ + + Copy the Warning story. + + + + Open the Button story in your current working directory. + + {buttonPath?.data && ( + // Replace '/' by '/' to properly break line + + {buttonPath.data.replaceAll('/', '/​').replaceAll('\\', '\\​')} + + )} + + + Paste it at the bottom of the file and save. + + +
+ + + {warningButtonStatus?.data ? ( + + ) : null} + + + ) : null)} +
+
+ + + + + +
+
+ )} +
+ ); +}; diff --git a/code/addons/onboarding/src/features/WriteStoriesModal/assets/01-title-sidebar.png b/code/addons/onboarding/src/features/WriteStoriesModal/assets/01-title-sidebar.png new file mode 100644 index 000000000000..064d9995bf1d Binary files /dev/null and b/code/addons/onboarding/src/features/WriteStoriesModal/assets/01-title-sidebar.png differ diff --git a/code/addons/onboarding/src/features/WriteStoriesModal/assets/02-story-name-sidebar.png b/code/addons/onboarding/src/features/WriteStoriesModal/assets/02-story-name-sidebar.png new file mode 100644 index 000000000000..1d562959c156 Binary files /dev/null and b/code/addons/onboarding/src/features/WriteStoriesModal/assets/02-story-name-sidebar.png differ diff --git a/code/addons/onboarding/src/features/WriteStoriesModal/assets/03-args.png b/code/addons/onboarding/src/features/WriteStoriesModal/assets/03-args.png new file mode 100644 index 000000000000..3312453658ae Binary files /dev/null and b/code/addons/onboarding/src/features/WriteStoriesModal/assets/03-args.png differ diff --git a/code/addons/onboarding/src/features/WriteStoriesModal/code/javascript.tsx b/code/addons/onboarding/src/features/WriteStoriesModal/code/javascript.tsx new file mode 100644 index 000000000000..2a91a7972e14 --- /dev/null +++ b/code/addons/onboarding/src/features/WriteStoriesModal/code/javascript.tsx @@ -0,0 +1,48 @@ +import type { CodeSnippets } from './types'; + +const data: CodeSnippets = { + filename: 'Button.stories.js', + language: 'typescript', + code: [ + [ + { + snippet: `import { Button } from './Button';`, + }, + ], + [ + { + snippet: `export default { + title: 'Example/Button', + component: Button, + // ... + };`, + }, + ], + [ + { snippet: `export const Primary = {` }, + { + snippet: `args: { + primary: true, + label: 'Click', + background: 'red' + }`, + toggle: true, + }, + { snippet: `};` }, + ], + [ + { + snippet: `// Copy the code below +export const Warning = { + args: { + primary: true, + label: 'Delete now', + backgroundColor: 'red', + } +};`, + }, + ], + ], +}; + +export default data; diff --git a/code/addons/onboarding/src/features/WriteStoriesModal/code/types.ts b/code/addons/onboarding/src/features/WriteStoriesModal/code/types.ts new file mode 100644 index 000000000000..130b4428f390 --- /dev/null +++ b/code/addons/onboarding/src/features/WriteStoriesModal/code/types.ts @@ -0,0 +1,6 @@ +export type CodeSnippets = { + framework?: string; + language: 'javascript' | 'typescript'; + filename: string; + code: { snippet: string; toggle?: boolean }[][]; +}; diff --git a/code/addons/onboarding/src/features/WriteStoriesModal/code/typescript.tsx b/code/addons/onboarding/src/features/WriteStoriesModal/code/typescript.tsx new file mode 100644 index 000000000000..45a578d8c6bd --- /dev/null +++ b/code/addons/onboarding/src/features/WriteStoriesModal/code/typescript.tsx @@ -0,0 +1,56 @@ +import type { CodeSnippets } from './types'; + +const data: CodeSnippets = { + filename: 'Button.stories.ts', + language: 'typescript', + code: [ + [ + { + snippet: `import type { Meta, StoryObj } from '@storybook/react'; + + import { Button } from './Button';`, + }, + ], + [ + { + snippet: `const meta: Meta = { + title: 'Example/Button', + component: Button, + // ... + }; + + export default meta;`, + }, + ], + [ + { + snippet: `type Story = StoryObj