diff --git a/.circleci/config.yml b/.circleci/config.yml index b212c825ddc2..5082db950b20 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -43,7 +43,7 @@ executors: default: "small" working_directory: /tmp/storybook docker: - - image: mcr.microsoft.com/playwright:v1.36.0-focal + - image: mcr.microsoft.com/playwright:v1.41.1-jammy environment: NODE_OPTIONS: --max_old_space_size=6144 resource_class: <> @@ -359,6 +359,19 @@ jobs: clone_options: "--depth 1 --verbose" - attach_workspace: at: . + - run: + name: Swap node versions + command: | + set +e + wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.1/install.sh | bash + export NVM_DIR="$HOME/.nvm" + [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" + [ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" + nvm install v18.14.0 + nvm alias default 18.14.0 + + echo 'export NVM_DIR="$HOME/.nvm"' >> $BASH_ENV + echo '[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"' >> $BASH_ENV - run: name: Running Test Runner command: yarn task --task test-runner --template $(yarn get-template --cadence << pipeline.parameters.workflow >> --task test-runner) --no-link --start-from=never --junit @@ -379,6 +392,19 @@ jobs: clone_options: "--depth 1 --verbose" - attach_workspace: at: . + - run: + name: Swap node versions + command: | + set +e + wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.1/install.sh | bash + export NVM_DIR="$HOME/.nvm" + [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" + [ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" + nvm install v18.14.0 + nvm alias default 18.14.0 + + echo 'export NVM_DIR="$HOME/.nvm"' >> $BASH_ENV + echo '[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"' >> $BASH_ENV - run: name: Running Test Runner in Dev mode command: yarn task --task test-runner-dev --template $(yarn get-template --cadence << pipeline.parameters.workflow >> --task test-runner-dev) --no-link --start-from=never --junit @@ -418,6 +444,19 @@ jobs: clone_options: "--depth 1 --verbose" - attach_workspace: at: . + - run: + name: Swap node versions + command: | + set +e + wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.1/install.sh | bash + export NVM_DIR="$HOME/.nvm" + [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" + [ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" + nvm install v18.14.0 + nvm alias default 18.14.0 + + echo 'export NVM_DIR="$HOME/.nvm"' >> $BASH_ENV + echo '[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"' >> $BASH_ENV - run: name: Running E2E Tests command: yarn task --task e2e-tests --template $(yarn get-template --cadence << pipeline.parameters.workflow >> --task e2e-tests) --no-link --start-from=never --junit @@ -441,6 +480,19 @@ jobs: clone_options: "--depth 1 --verbose" - attach_workspace: at: . + - run: + name: Swap node versions + command: | + set +e + wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.1/install.sh | bash + export NVM_DIR="$HOME/.nvm" + [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" + [ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" + nvm install v18.14.0 + nvm alias default 18.14.0 + + echo 'export NVM_DIR="$HOME/.nvm"' >> $BASH_ENV + echo '[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"' >> $BASH_ENV - run: name: Running E2E Tests command: yarn task --task e2e-tests-dev --template $(yarn get-template --cadence << pipeline.parameters.workflow >> --task e2e-tests-dev) --no-link --start-from=never --junit @@ -464,6 +516,19 @@ jobs: clone_options: "--depth 1 --verbose" - attach_workspace: at: . + - run: + name: Swap node versions + command: | + set +e + wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.1/install.sh | bash + export NVM_DIR="$HOME/.nvm" + [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" + [ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" + nvm install v18.14.0 + nvm alias default 18.14.0 + + echo 'export NVM_DIR="$HOME/.nvm"' >> $BASH_ENV + echo '[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"' >> $BASH_ENV - run: name: Running Bench command: yarn task --task bench --template $(yarn get-template --cadence << pipeline.parameters.workflow >> --task bench) --no-link --start-from=never --junit diff --git a/MIGRATION.md b/MIGRATION.md index e02b78ed92ca..6c4a05c8b8b5 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -70,6 +70,7 @@ - [`createChannel` from `@storybook/postmessage` and `@storybook/channel-websocket`](#createchannel-from-storybookpostmessage-and-storybookchannel-websocket) - [StoryStore and methods deprecated](#storystore-and-methods-deprecated) - [Addon author changes](#addon-author-changes) + - [Tab addons are now routed to a query parameter](#tab-addons-are-now-routed-to-a-query-parameter) - [Removed `config` preset](#removed-config-preset-1) - [From version 7.5.0 to 7.6.0](#from-version-750-to-760) - [CommonJS with Vite is deprecated](#commonjs-with-vite-is-deprecated) @@ -1050,6 +1051,48 @@ Note that both these methods require initialization, so you should await `previe ### Addon author changes +#### Tab addons are now routed to a query parameter + +The TAB type addons now should no longer specify the `match` or `route` property. + +Instead storybook will automatically show the addon's rendered content when the query parameter `tab` is set to the addon's ID. + +Example: + +```tsx +import { addons, types } from "@storybook/manager-api"; + +addons.register("my-addon", () => { + addons.add("my-addon/panel", { + type: types.TAB, + title: "My Addon", + render: () =>
Hello World
, + }); +}); +``` + +Tool type addon will now receive the `tabId` property passed to their `match` function. +That way they can chose to show/hide their content based on the current tab. + +When the canvas is shown, the `tabId` will be set to `undefined`. + +Example: + +```tsx +import { addons, types } from "@storybook/manager-api"; + +addons.register("my-addon", () => { + addons.add("my-addon/tool", { + type: types.TOOL, + title: "My Addon", + match: ({ tabId }) => tabId === "my-addon/panel", + render: () =>
👀
, + }); +}); +``` + +The URL the tab gets rendered on will be `http://localhost:6006/?path=/story/my-story&tab=my-addon/tab`. + #### Removed `config` preset In Storybook 7.0 we have deprecated the preset field `config` and it has been replaced with `previewAnnotations`. The `config` preset is now completely removed in Storybook 8.0. diff --git a/code/addons/a11y/src/manager.tsx b/code/addons/a11y/src/manager.tsx index 3943fb3d2d48..03a1bf1ec853 100644 --- a/code/addons/a11y/src/manager.tsx +++ b/code/addons/a11y/src/manager.tsx @@ -29,7 +29,7 @@ addons.register(ADDON_ID, (api) => { addons.add(PANEL_ID, { title: '', type: types.TOOL, - match: ({ viewMode }) => viewMode === 'story', + match: ({ viewMode, tabId }) => viewMode === 'story' && !tabId, render: () => , }); diff --git a/code/addons/backgrounds/src/manager.tsx b/code/addons/backgrounds/src/manager.tsx index ecd36b0bb618..cef974b6551b 100644 --- a/code/addons/backgrounds/src/manager.tsx +++ b/code/addons/backgrounds/src/manager.tsx @@ -9,7 +9,7 @@ addons.register(ADDON_ID, () => { addons.add(ADDON_ID, { title: 'Backgrounds', type: types.TOOL, - match: ({ viewMode }) => !!(viewMode && viewMode.match(/^(story|docs)$/)), + match: ({ viewMode, tabId }) => !!(viewMode && viewMode.match(/^(story|docs)$/)) && !tabId, render: () => ( diff --git a/code/addons/measure/src/manager.tsx b/code/addons/measure/src/manager.tsx index 53bca4d0b716..bb51e2c254ca 100644 --- a/code/addons/measure/src/manager.tsx +++ b/code/addons/measure/src/manager.tsx @@ -8,7 +8,7 @@ addons.register(ADDON_ID, () => { addons.add(TOOL_ID, { type: types.TOOL, title: 'Measure', - match: ({ viewMode }) => viewMode === 'story', + match: ({ viewMode, tabId }) => viewMode === 'story' && !tabId, render: () => , }); }); diff --git a/code/addons/outline/src/manager.tsx b/code/addons/outline/src/manager.tsx index 384ea24c07c6..3c80679a5fc9 100644 --- a/code/addons/outline/src/manager.tsx +++ b/code/addons/outline/src/manager.tsx @@ -8,7 +8,7 @@ addons.register(ADDON_ID, () => { addons.add(ADDON_ID, { title: 'Outline', type: types.TOOL, - match: ({ viewMode }) => !!(viewMode && viewMode.match(/^(story|docs)$/)), + match: ({ viewMode, tabId }) => !!(viewMode && viewMode.match(/^(story|docs)$/)) && !tabId, render: () => , }); }); diff --git a/code/addons/themes/docs/getting-started/postcss.md b/code/addons/themes/docs/getting-started/postcss.md index 3748dd272a10..d6ce1c8f02b1 100644 --- a/code/addons/themes/docs/getting-started/postcss.md +++ b/code/addons/themes/docs/getting-started/postcss.md @@ -64,7 +64,7 @@ Use `prefers-color-scheme` media in your CSS: } @media (prefers-color-scheme: dark) { html { - --text-color: white + --text-color: white; } } ``` diff --git a/code/addons/themes/src/manager.tsx b/code/addons/themes/src/manager.tsx index 2fba5bd29a4b..f67c21c0b99c 100644 --- a/code/addons/themes/src/manager.tsx +++ b/code/addons/themes/src/manager.tsx @@ -7,7 +7,7 @@ addons.register(ADDON_ID, () => { addons.add(THEME_SWITCHER_ID, { title: 'Themes', type: types.TOOL, - match: ({ viewMode }) => !!(viewMode && viewMode.match(/^(story|docs)$/)), + match: ({ viewMode, tabId }) => !!(viewMode && viewMode.match(/^(story|docs)$/)) && !tabId, render: ThemeSwitcher, paramKey: PARAM_KEY, }); diff --git a/code/addons/toolbars/src/manager.tsx b/code/addons/toolbars/src/manager.tsx index c87d3fbf2d80..f1edac1d2fd5 100644 --- a/code/addons/toolbars/src/manager.tsx +++ b/code/addons/toolbars/src/manager.tsx @@ -7,7 +7,7 @@ addons.register(ADDON_ID, () => addons.add(ADDON_ID, { title: ADDON_ID, type: types.TOOL, - match: () => true, + match: ({ tabId }) => !tabId, render: () => , }) ); diff --git a/code/addons/viewport/src/manager.tsx b/code/addons/viewport/src/manager.tsx index e42e3fba40e1..973df25422c4 100644 --- a/code/addons/viewport/src/manager.tsx +++ b/code/addons/viewport/src/manager.tsx @@ -9,7 +9,7 @@ addons.register(ADDON_ID, () => { addons.add(ADDON_ID, { title: 'viewport / media-queries', type: types.TOOL, - match: ({ viewMode }) => viewMode === 'story', + match: ({ viewMode, tabId }) => viewMode === 'story' && !tabId, render: () => , }); }); diff --git a/code/lib/manager-api/src/modules/url.ts b/code/lib/manager-api/src/modules/url.ts index e9431f04a1f5..8896ab272899 100644 --- a/code/lib/manager-api/src/modules/url.ts +++ b/code/lib/manager-api/src/modules/url.ts @@ -144,6 +144,12 @@ export interface SubAPI { * @returns {void} */ setQueryParams: (input: QueryParams) => void; + /** + * Set the query parameters for the current URL & navigates. + * @param {QueryParams} input - An object containing the query parameters to set. + * @returns {void} + */ + applyQueryParams: (input: QueryParams) => void; } export const init: ModuleFn = (moduleArgs) => { @@ -188,6 +194,12 @@ export const init: ModuleFn = (moduleArgs) => { provider.channel?.emit(UPDATE_QUERY_PARAMS, update); } }, + applyQueryParams(input) { + const { path, queryParams } = api.getUrlState(); + + navigateTo(path, { ...queryParams, ...input } as any); + api.setQueryParams(input); + }, navigateUrl(url, options) { navigate(url, { plain: true, ...options }); }, diff --git a/code/lib/types/src/modules/addons.ts b/code/lib/types/src/modules/addons.ts index dd238ccaceaf..fe4fa8551cfa 100644 --- a/code/lib/types/src/modules/addons.ts +++ b/code/lib/types/src/modules/addons.ts @@ -351,7 +351,7 @@ export interface Addon_BaseType { /** * This will determine the value of `active` prop of your render function. */ - match?: (matchOptions: RouterData) => boolean; + match?: (matchOptions: RouterData & { tabId?: string }) => boolean; /** * The actual contents of your addon. * @@ -423,7 +423,6 @@ export interface Addon_WrapperType { children: ReactNode; id: string; storyId: StoryId; - active: boolean; }> >; } diff --git a/code/package.json b/code/package.json index 415410b1cdff..e813ddf3ae86 100644 --- a/code/package.json +++ b/code/package.json @@ -76,18 +76,18 @@ "defaults" ], "resolutions": { - "@playwright/test": "1.36.0", + "@playwright/test": "1.41.1", "@storybook/theming": "workspace:*", "@vitest/expect@1.1.3": "patch:@vitest/expect@npm%3A1.1.3#~/.yarn/patches/@vitest-expect-npm-1.1.3-2062bf533f.patch", "esbuild": "^0.18.0", - "playwright": "1.36.0", - "playwright-core": "1.36.0", + "playwright": "1.41.1", + "playwright-core": "1.41.1", "serialize-javascript": "^3.1.0", "type-fest": "~2.19" }, "dependencies": { "@nx/workspace": "17.0.2", - "@playwright/test": "1.36.0", + "@playwright/test": "1.41.1", "@storybook/addon-a11y": "workspace:*", "@storybook/addon-actions": "workspace:*", "@storybook/addon-backgrounds": "workspace:*", diff --git a/code/ui/manager/src/App.tsx b/code/ui/manager/src/App.tsx index 74e68183ee62..7ec337525c10 100644 --- a/code/ui/manager/src/App.tsx +++ b/code/ui/manager/src/App.tsx @@ -13,15 +13,17 @@ type Props = { managerLayoutState: ComponentProps['managerLayoutState']; setManagerLayoutState: ComponentProps['setManagerLayoutState']; pages: Addon_PageType[]; + hasTab: boolean; }; -export const App = ({ managerLayoutState, setManagerLayoutState, pages }: Props) => { +export const App = ({ managerLayoutState, setManagerLayoutState, pages, hasTab }: Props) => { const { setMobileAboutOpen } = useLayout(); return ( <> } diff --git a/code/ui/manager/src/components/layout/Layout.stories.tsx b/code/ui/manager/src/components/layout/Layout.stories.tsx index 6fa6541b88df..6c291c5f2fe8 100644 --- a/code/ui/manager/src/components/layout/Layout.stories.tsx +++ b/code/ui/manager/src/components/layout/Layout.stories.tsx @@ -61,6 +61,7 @@ const meta = { slotPanel: , slotPages: , setManagerLayoutState: fn(), + hasTab: false, }, parameters: { theme: 'light', diff --git a/code/ui/manager/src/components/layout/Layout.tsx b/code/ui/manager/src/components/layout/Layout.tsx index 1c3494f76a20..a17636ceadbd 100644 --- a/code/ui/manager/src/components/layout/Layout.tsx +++ b/code/ui/manager/src/components/layout/Layout.tsx @@ -26,6 +26,7 @@ interface Props { slotSidebar?: React.ReactNode; slotPanel?: React.ReactNode; slotPages?: React.ReactNode; + hasTab: boolean; } const MINIMUM_CONTENT_WIDTH_PX = 100; @@ -45,10 +46,12 @@ const useLayoutSyncingState = ({ managerLayoutState, setManagerLayoutState, isDesktop, + hasTab, }: { managerLayoutState: Props['managerLayoutState']; setManagerLayoutState: Props['setManagerLayoutState']; isDesktop: boolean; + hasTab: boolean; }) => { // ref to keep track of previous managerLayoutState, to check if the props change const prevManagerLayoutStateRef = React.useRef(managerLayoutState); @@ -96,7 +99,7 @@ const useLayoutSyncingState = ({ const isPagesShown = managerLayoutState.viewMode !== 'story' && managerLayoutState.viewMode !== 'docs'; - const isPanelShown = managerLayoutState.viewMode === 'story'; + const isPanelShown = managerLayoutState.viewMode === 'story' && !hasTab; const { panelResizerRef, sidebarResizerRef } = useDragging({ setState: setInternalDraggingSizeState, @@ -120,7 +123,7 @@ const useLayoutSyncingState = ({ }; }; -export const Layout = ({ managerLayoutState, setManagerLayoutState, ...slots }: Props) => { +export const Layout = ({ managerLayoutState, setManagerLayoutState, hasTab, ...slots }: Props) => { const { isDesktop, isMobile } = useLayout(); const { @@ -133,7 +136,7 @@ export const Layout = ({ managerLayoutState, setManagerLayoutState, ...slots }: showPages, showPanel, isDragging, - } = useLayoutSyncingState({ managerLayoutState, setManagerLayoutState, isDesktop }); + } = useLayoutSyncingState({ managerLayoutState, setManagerLayoutState, isDesktop, hasTab }); return ( {showPages && {slots.slotPages}} @@ -174,8 +178,8 @@ export const Layout = ({ managerLayoutState, setManagerLayoutState, ...slots }: ); }; -const LayoutContainer = styled.div( - ({ navSize, rightPanelWidth, bottomPanelHeight, viewMode, panelPosition }) => { +const LayoutContainer = styled.div( + ({ navSize, rightPanelWidth, bottomPanelHeight, viewMode, panelPosition, showPanel }) => { return { width: '100%', height: ['100vh', '100dvh'], // This array is a special Emotion syntax to set a fallback if 100dvh is not supported @@ -189,7 +193,7 @@ const LayoutContainer = styled.div( gridTemplateColumns: `minmax(0, ${navSize}px) minmax(${MINIMUM_CONTENT_WIDTH_PX}px, 1fr) minmax(0, ${rightPanelWidth}px)`, gridTemplateRows: `1fr minmax(0, ${bottomPanelHeight}px)`, gridTemplateAreas: (() => { - if (viewMode === 'docs') { + if (viewMode === 'docs' || !showPanel) { // remove panel in docs viewMode return `"sidebar content content" "sidebar content content"`; diff --git a/code/ui/manager/src/components/preview/Preview.mockdata.tsx b/code/ui/manager/src/components/preview/Preview.mockdata.tsx deleted file mode 100644 index 29d72ec355a2..000000000000 --- a/code/ui/manager/src/components/preview/Preview.mockdata.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { types } from '@storybook/manager-api'; -import type { API, State } from '@storybook/manager-api'; -import type { Addon_BaseType, Addon_Collection } from '@storybook/types'; -import type { PreviewProps } from './utils/types'; - -const addonNotes: Addon_BaseType = { - id: 'notes', - type: types.TAB, - title: 'Notes', - route: ({ storyId }) => `/info/${storyId}`, - match: ({ viewMode }) => viewMode === 'info', - render: () => null, -}; - -const mockAPI: Partial = { - on: (a, b) => () => {}, - emit: () => {}, - off: () => {}, - getElements: (type) => - type === types.TAB ? ({ notes: addonNotes } as Addon_Collection) : {}, -}; - -export const previewProps: PreviewProps = { - id: 'string', - storyId: 'story--id', - api: mockAPI as API, - entry: { - tags: [], - type: 'story', - id: 'story--id', - parent: 'root', - depth: 1, - title: 'kind', - name: 'story name', - importPath: './story.stories.tsx', - prepared: true, - parameters: { - fileName: '', - options: {}, - }, - args: {}, - }, - path: 'string', - viewMode: 'story', - location: {} as any as State['location'], - baseUrl: 'http://example.com', - queryParams: {}, - options: { - showTabs: true, - showToolbar: true, - }, - withLoader: false, - description: '', - refs: {}, -}; diff --git a/code/ui/manager/src/components/preview/Preview.stories.tsx b/code/ui/manager/src/components/preview/Preview.stories.tsx deleted file mode 100644 index 0a639c9db64e..000000000000 --- a/code/ui/manager/src/components/preview/Preview.stories.tsx +++ /dev/null @@ -1,169 +0,0 @@ -import React from 'react'; - -import { parsePath, createPath } from 'history'; -import type { Combo, StoryEntry } from '@storybook/manager-api'; -import { Provider as ManagerProvider, Consumer } from '@storybook/manager-api'; -import { Location, BaseLocationProvider } from '@storybook/router'; - -import { ThemeProvider, ensure as ensureTheme, themes } from '@storybook/theming'; - -import type { Decorator } from '@storybook/react'; -import { Preview } from './Preview'; - -import { PrettyFakeProvider } from '../../FakeProvider'; -import { previewProps } from './Preview.mockdata'; -import { LayoutProvider } from '../layout/LayoutProvider'; - -const provider = new PrettyFakeProvider(); -const staticNavigator = { - createHref(to: any) { - return typeof to === 'string' ? to : createPath(to); - }, - - push() {}, - - replace() {}, - - go() {}, - - back() {}, - - forward() {}, -}; - -export default { - title: 'Preview', - component: Preview, - parameters: { - layout: 'fullscreen', - theme: 'light', - }, - decorators: [ - ((StoryFn, c) => { - const locationProp = parsePath('/?path=/story/story--id'); - - const location = { - pathname: locationProp.pathname || '/', - search: locationProp.search || '', - hash: locationProp.hash || '', - // @ts-expect-error (invalid input) - state: null, - key: 'default', - }; - - return ( - - - {(locationData) => ( - {}} - > - - - - - - - )} - - - ); - }) as Decorator, - ], -}; - -export const NoTabs = () => ( - - {({ api }: Combo) => { - return ( - ({}) }} - entry={{ - ...(previewProps.entry as StoryEntry), - parameters: { previewTabs: { canvas: { hidden: true } } }, - }} - /> - ); - }} - -); - -export const HideFullscreen = () => ( - - {({ api }: Combo) => { - return ( - ({}) }} - entry={{ - ...(previewProps.entry as StoryEntry), - parameters: { toolbar: { fullscreen: { hidden: true } } }, - }} - /> - ); - }} - -); - -export const HideAllDefaultTools = () => ( - - {({ api }: Combo) => { - return ( - ({}) }} - entry={{ - ...(previewProps.entry as StoryEntry), - parameters: { - toolbar: { - title: { hidden: true }, - remount: { hidden: true }, - zoom: { hidden: true }, - eject: { hidden: true }, - copy: { hidden: true }, - fullscreen: { hidden: true }, - }, - }, - }} - /> - ); - }} - -); - -export const WithCanvasTab = () => ( - - {({ api }: Combo) => { - return ({}) }} />; - }} - -); - -export const WithTabs = () => ; - -export const WithTabsHidden = () => ( - - {({ api }: Combo) => { - return ( - ({}) }} - /> - ); - }} - -); diff --git a/code/ui/manager/src/components/preview/Preview.tsx b/code/ui/manager/src/components/preview/Preview.tsx index 3130cccb3c19..63d2a7fb8601 100644 --- a/code/ui/manager/src/components/preview/Preview.tsx +++ b/code/ui/manager/src/components/preview/Preview.tsx @@ -1,26 +1,22 @@ import type { FC } from 'react'; -import React, { Fragment, useMemo, useEffect, useRef, useState } from 'react'; +import React, { Fragment, useEffect, useRef, useState } from 'react'; import { Helmet } from 'react-helmet-async'; import { global } from '@storybook/global'; -import { type API, Consumer, type Combo, merge, addons, types } from '@storybook/manager-api'; -import type { Addon_BaseType } from '@storybook/types'; +import { Consumer, type Combo, merge, addons, types } from '@storybook/manager-api'; +import type { Addon_BaseType, Addon_WrapperType } from '@storybook/types'; import { PREVIEW_BUILDER_PROGRESS, SET_CURRENT_STORY } from '@storybook/core-events'; import { Loader } from '@storybook/components'; -import { Location } from '@storybook/router'; import * as S from './utils/components'; import { ZoomProvider, ZoomConsumer } from './tools/zoom'; -import { defaultWrappers, ApplyWrappers } from './Wrappers'; +import { ApplyWrappers } from './Wrappers'; import { ToolbarComp } from './Toolbar'; import { FramesRenderer } from './FramesRenderer'; import type { PreviewProps } from './utils/types'; -const getWrappers = (getFn: API['getElements']) => Object.values(getFn(types.PREVIEW)); -const getTabs = (getFn: API['getElements']) => Object.values(getFn(types.TAB)); - const canvasMapper = ({ state, api }: Combo) => ({ storyId: state.storyId, refId: state.refId, @@ -31,10 +27,9 @@ const canvasMapper = ({ state, api }: Combo) => ({ entry: api.getData(state.storyId, state.refId), previewInitialized: state.previewInitialized, refs: state.refs, - active: !!(state.viewMode && state.viewMode.match(/^(story|docs)$/)), }); -const createCanvasTab = (): Addon_BaseType => ({ +export const createCanvasTab = (): Addon_BaseType => ({ id: 'canvas', type: types.TAB, title: 'Canvas', @@ -43,19 +38,6 @@ const createCanvasTab = (): Addon_BaseType => ({ render: () => null, }); -const useTabs = (getElements: API['getElements'], entry: PreviewProps['entry']) => { - const canvasTab = useMemo(() => createCanvasTab(), []); - const tabsFromConfig = useMemo(() => getTabs(getElements), [getElements]); - - return useMemo(() => { - if (entry?.type === 'story' && entry.parameters) { - return filterTabs([canvasTab, ...tabsFromConfig], entry.parameters); - } - - return [canvasTab, ...tabsFromConfig]; - }, [entry, ...tabsFromConfig]); -}; - const Preview = React.memo(function Preview(props) { const { api, @@ -67,34 +49,39 @@ const Preview = React.memo(function Preview(props) { description, baseUrl, withLoader = true, + tools, + toolsExtra, + tabs, + wrappers, + tabId, } = props; - const { getElements } = api; - const tabs = useTabs(getElements, entry); + const tabContent = tabs.find((tab) => tab.id === tabId)?.render; const shouldScale = viewMode === 'story'; - const { showToolbar, showTabs = true } = options; - const visibleTabsInToolbar = showTabs ? tabs : []; + const { showToolbar } = options; const previousStoryId = useRef(storyId); useEffect(() => { if (entry && viewMode) { // Don't emit the event on first ("real") render, only when entry changes - if (storyId !== previousStoryId.current) { - previousStoryId.current = storyId; + if (storyId === previousStoryId.current) { + return; + } - if (viewMode.match(/docs|story/)) { - const { refId, id } = entry; - api.emit(SET_CURRENT_STORY, { - storyId: id, - viewMode, - options: { target: refId }, - }); - } + previousStoryId.current = storyId; + + if (viewMode.match(/docs|story/)) { + const { refId, id } = entry; + api.emit(SET_CURRENT_STORY, { + storyId: id, + viewMode, + options: { target: refId }, + }); } } - }, [entry, viewMode]); + }, [entry, viewMode, storyId, api]); return ( @@ -107,22 +94,18 @@ const Preview = React.memo(function Preview(props) { - - {tabs.map(({ render: Render, match, ...t }, i) => { - // @ts-expect-error (Converted from ts-ignore) - const key = t.id || t.key || i; - return ( - - {(lp) => } - - ); - })} + {tabContent && {tabContent({ active: true })}} + + + @@ -132,10 +115,12 @@ const Preview = React.memo(function Preview(props) { export { Preview }; -const Canvas: FC<{ withLoader: boolean; baseUrl: string; children?: never }> = ({ - baseUrl, - withLoader, -}) => { +const Canvas: FC<{ + withLoader: boolean; + baseUrl: string; + children?: never; + wrappers: Addon_WrapperType[]; +}> = ({ baseUrl, withLoader, wrappers }) => { return ( {({ @@ -146,15 +131,9 @@ const Canvas: FC<{ withLoader: boolean; baseUrl: string; children?: never }> = ( refId, viewMode, queryParams, - getElements, previewInitialized, - active, }) => { const id = 'canvas'; - const wrappers = useMemo( - () => [...defaultWrappers, ...getWrappers(getElements)], - [getElements, ...defaultWrappers] - ); const [progress, setProgress] = useState(undefined); useEffect(() => { @@ -187,13 +166,7 @@ const Canvas: FC<{ withLoader: boolean; baseUrl: string; children?: never }> = ( )} - + {customCanvas ? ( customCanvas(storyId, viewMode, id, baseUrl, scale, queryParams) ) : ( @@ -219,7 +192,7 @@ const Canvas: FC<{ withLoader: boolean; baseUrl: string; children?: never }> = ( ); }; -function filterTabs(panels: Addon_BaseType[], parameters: Record) { +export function filterTabs(panels: Addon_BaseType[], parameters?: Record | undefined) { const { previewTabs } = addons.getConfig(); const parametersTabs = parameters ? parameters.previewTabs : undefined; diff --git a/code/ui/manager/src/components/preview/Toolbar.tsx b/code/ui/manager/src/components/preview/Toolbar.tsx index b09d8f3e2a57..f46f3489790f 100644 --- a/code/ui/manager/src/components/preview/Toolbar.tsx +++ b/code/ui/manager/src/components/preview/Toolbar.tsx @@ -1,5 +1,4 @@ -import type { FunctionComponent } from 'react'; -import React, { Fragment, useMemo } from 'react'; +import React, { Fragment } from 'react'; import { styled } from '@storybook/theming'; @@ -16,17 +15,13 @@ import { types, } from '@storybook/manager-api'; -import { Location, type RenderData } from '@storybook/router'; import { Addon_TypesEnum, type Addon_BaseType } from '@storybook/types'; import { CloseIcon, ExpandIcon } from '@storybook/icons'; import { zoomTool } from './tools/zoom'; -import * as S from './utils/components'; - import type { PreviewProps } from './utils/types'; import { copyTool } from './tools/copy'; import { ejectTool } from './tools/eject'; -import { menuTool } from './tools/menu'; import { addonsTool } from './tools/addons'; import { remountTool } from './tools/remount'; import { useLayout } from '../layout/LayoutProvider'; @@ -73,12 +68,10 @@ export const fullScreenTool: Addon_BaseType = { }, }; -const tabsMapper = ({ state }: Combo) => ({ - viewMode: state.docsOnly, - storyId: state.storyId, +const tabsMapper = ({ api, state }: Combo) => ({ + navigate: api.navigate, path: state.path, - location: state.location, - refId: state.refId, + applyQueryParams: api.applyQueryParams, }); export const createTabsTool = (tabs: Addon_BaseType[]): Addon_BaseType => ({ @@ -91,16 +84,21 @@ export const createTabsTool = (tabs: Addon_BaseType[]): Addon_BaseType => ({ {tabs - .filter((p) => !p.hidden) - .map((t, index) => { - const to = t.route(rp); - const isActive = rp.path === to; + .filter(({ hidden }) => !hidden) + .map((tab, index) => { + const tabIdToApply = tab.id === 'canvas' ? undefined : tab.id; + const isActive = rp.path.includes(`tab=${tab.id}`); return ( - - - {t.title as any} - - + { + rp.applyQueryParams({ tab: tabIdToApply }); + }} + key={tab.id || `tab-${index}`} + > + {tab.title as any} + ); })} @@ -119,73 +117,56 @@ export const defaultToolsExtra: Addon_BaseType[] = [ copyTool, ]; -const useTools = ( - getElements: API['getElements'], - tabs: Addon_BaseType[], - viewMode: PreviewProps['viewMode'], - entry: PreviewProps['entry'], - location: PreviewProps['location'], - path: PreviewProps['path'] -) => { - const toolsFromConfig = useMemo( - () => getTools(getElements), - [getElements, getTools(getElements).length] - ); - const toolsExtraFromConfig = useMemo(() => getToolsExtra(getElements), [getElements]); - - const tools = useMemo( - () => [...defaultTools, ...toolsFromConfig], - [defaultTools, toolsFromConfig] - ); - const toolsExtra = useMemo( - () => [...defaultToolsExtra, ...toolsExtraFromConfig], - [defaultToolsExtra, toolsExtraFromConfig] - ); - - return useMemo(() => { - return ['story', 'docs'].includes(entry?.type) - ? filterTools(tools, toolsExtra, tabs, { - viewMode, - entry, - location, - path, - }) - : { left: tools, right: toolsExtra }; - }, [viewMode, entry, location, path, tools, toolsExtra, tabs]); -}; - export interface ToolData { isShown: boolean; tabs: Addon_BaseType[]; + tools: Addon_BaseType[]; + tabId: string; + toolsExtra: Addon_BaseType[]; api: API; - entry: LeafEntry; } -export const ToolRes: FunctionComponent = React.memo( - function ToolRes({ api, entry, tabs, isShown, location, path, viewMode }) { - const { left, right } = useTools(api.getElements, tabs, viewMode, entry, location, path); - - return left || right ? ( - - - - - - - - - - - ) : null; - } -); - -export const ToolbarComp = React.memo(function ToolbarComp(props) { - return ( - - {({ location, path, viewMode }) => } - - ); +export const ToolbarComp = React.memo(function ToolbarComp({ + isShown, + tools, + toolsExtra, + tabs, + tabId, + api, +}) { + return tabs || tools || toolsExtra ? ( + + + + {tabs.length > 1 ? ( + + + {tabs.map((tab, index) => { + return ( + { + api.applyQueryParams({ tab: tab.id === 'canvas' ? undefined : tab.id }); + }} + key={tab.id || `tab-${index}`} + > + {tab.title as any} + + ); + })} + + + + ) : null} + + + + + + + + ) : null; }); export const Tools = React.memo<{ list: Addon_BaseType[] }>(function Tools({ list }) { @@ -199,55 +180,38 @@ export const Tools = React.memo<{ list: Addon_BaseType[] }>(function Tools({ lis ); }); -function toolbarItemHasBeenExcluded(item: Partial, entry: LeafEntry) { - const parameters = entry.type === 'story' && entry.prepared ? entry.parameters : {}; +function toolbarItemHasBeenExcluded(item: Partial, entry: LeafEntry | undefined) { + const parameters = entry?.type === 'story' && entry?.prepared ? entry?.parameters : {}; const toolbarItemsFromStoryParameters = 'toolbar' in parameters ? parameters.toolbar : undefined; const { toolbar: toolbarItemsFromAddonsConfig } = addons.getConfig(); const toolbarItems = merge(toolbarItemsFromAddonsConfig, toolbarItemsFromStoryParameters); - return toolbarItems ? !!toolbarItems[item.id]?.hidden : false; + return toolbarItems ? !!toolbarItems[item?.id]?.hidden : false; } -export function filterTools( +export function filterToolsSide( tools: Addon_BaseType[], - toolsExtra: Addon_BaseType[], - tabs: Addon_BaseType[], - { - viewMode, - entry, - location, - path, - }: { - viewMode: State['viewMode']; - entry: PreviewProps['entry']; - location: State['location']; - path: State['path']; - } + entry: PreviewProps['entry'], + viewMode: State['viewMode'], + location: State['location'], + path: State['path'], + tabId: string ) { - const toolsLeft = [ - menuTool, - tabs.filter((p) => !p.hidden).length > 1 && createTabsTool(tabs), - ...tools, - ]; - const toolsRight = [...toolsExtra]; - const filter = (item: Partial) => item && (!item.match || item.match({ - storyId: entry.id, - refId: entry.refId, + storyId: entry?.id, + refId: entry?.refId, viewMode, location, path, + tabId, })) && !toolbarItemHasBeenExcluded(item, entry); - const left = toolsLeft.filter(filter); - const right = toolsRight.filter(filter); - - return { left, right }; + return tools.filter(filter); } const Toolbar = styled.div<{ shown: boolean }>(({ theme, shown }) => ({ diff --git a/code/ui/manager/src/components/preview/Wrappers.tsx b/code/ui/manager/src/components/preview/Wrappers.tsx index 80640a9875fa..b70385e928fd 100644 --- a/code/ui/manager/src/components/preview/Wrappers.tsx +++ b/code/ui/manager/src/components/preview/Wrappers.tsx @@ -9,14 +9,13 @@ export const ApplyWrappers: FC> = ({ wrappers, id, storyId, - active, children, }) => { return ( {wrappers.reduceRight( (acc, wrapper, index) => ( - + ), children )} @@ -28,10 +27,6 @@ export const defaultWrappers: Addon_WrapperType[] = [ { id: 'iframe-wrapper', type: Addon_TypesEnum.PREVIEW, - render: (p) => ( - - ), + render: (p) => {p.children}, }, ]; diff --git a/code/ui/manager/src/components/preview/tools/addons.tsx b/code/ui/manager/src/components/preview/tools/addons.tsx index 5af0f4bf7040..956a327ed840 100644 --- a/code/ui/manager/src/components/preview/tools/addons.tsx +++ b/code/ui/manager/src/components/preview/tools/addons.tsx @@ -16,7 +16,7 @@ export const addonsTool: Addon_BaseType = { title: 'addons', id: 'addons', type: types.TOOL, - match: ({ viewMode }) => viewMode === 'story', + match: ({ viewMode, tabId }) => viewMode === 'story' && !tabId, render: () => ( {({ isVisible, toggle, singleStory, panelPosition }) => diff --git a/code/ui/manager/src/components/preview/tools/copy.tsx b/code/ui/manager/src/components/preview/tools/copy.tsx index f06e926c0290..59d72f3de850 100644 --- a/code/ui/manager/src/components/preview/tools/copy.tsx +++ b/code/ui/manager/src/components/preview/tools/copy.tsx @@ -28,7 +28,7 @@ export const copyTool: Addon_BaseType = { title: 'copy', id: 'copy', type: types.TOOL, - match: ({ viewMode }) => viewMode === 'story', + match: ({ viewMode, tabId }) => viewMode === 'story' && !tabId, render: () => ( {({ baseUrl, storyId, queryParams }) => diff --git a/code/ui/manager/src/components/preview/tools/eject.tsx b/code/ui/manager/src/components/preview/tools/eject.tsx index e8c65f8fd29b..41c091ccc582 100644 --- a/code/ui/manager/src/components/preview/tools/eject.tsx +++ b/code/ui/manager/src/components/preview/tools/eject.tsx @@ -24,7 +24,7 @@ export const ejectTool: Addon_BaseType = { title: 'eject', id: 'eject', type: types.TOOL, - match: ({ viewMode }) => viewMode === 'story', + match: ({ viewMode, tabId }) => viewMode === 'story' && !tabId, render: () => ( {({ baseUrl, storyId, queryParams }) => diff --git a/code/ui/manager/src/components/preview/tools/remount.tsx b/code/ui/manager/src/components/preview/tools/remount.tsx index af85a43af246..ecc178a41677 100644 --- a/code/ui/manager/src/components/preview/tools/remount.tsx +++ b/code/ui/manager/src/components/preview/tools/remount.tsx @@ -34,7 +34,7 @@ export const remountTool: Addon_BaseType = { title: 'remount', id: 'remount', type: types.TOOL, - match: ({ viewMode }) => viewMode === 'story', + match: ({ viewMode, tabId }) => viewMode === 'story' && !tabId, render: () => ( {({ remount, storyId, api }) => { diff --git a/code/ui/manager/src/components/preview/tools/zoom.tsx b/code/ui/manager/src/components/preview/tools/zoom.tsx index e38cb99e0526..cffe05ac4d0e 100644 --- a/code/ui/manager/src/components/preview/tools/zoom.tsx +++ b/code/ui/manager/src/components/preview/tools/zoom.tsx @@ -97,6 +97,6 @@ export const zoomTool: Addon_BaseType = { title: 'zoom', id: 'zoom', type: types.TOOL, - match: ({ viewMode }) => viewMode === 'story', + match: ({ viewMode, tabId }) => viewMode === 'story' && !tabId, render: ZoomToolRenderer, }; diff --git a/code/ui/manager/src/components/preview/utils/components.ts b/code/ui/manager/src/components/preview/utils/components.ts index b5b446aec116..1e6e358f8fa0 100644 --- a/code/ui/manager/src/components/preview/utils/components.ts +++ b/code/ui/manager/src/components/preview/utils/components.ts @@ -16,6 +16,22 @@ export const FrameWrap = styled.div({ background: 'transparent', flex: 1, }); +export const CanvasWrap = styled.div<{ display: boolean }>( + { + alignContent: 'center', + alignItems: 'center', + justifyContent: 'center', + justifyItems: 'center', + overflow: 'auto', + display: 'grid', + gridTemplateColumns: '100%', + gridTemplateRows: '100%', + position: 'relative', + width: '100%', + height: '100%', + }, + ({ display }) => (display ? {} : { display: 'none' }) +); export const UnstyledLink = styled(Link)({ color: 'inherit', diff --git a/code/ui/manager/src/components/preview/utils/types.tsx b/code/ui/manager/src/components/preview/utils/types.tsx index 90ae75246019..7b0e60caefb5 100644 --- a/code/ui/manager/src/components/preview/utils/types.tsx +++ b/code/ui/manager/src/components/preview/utils/types.tsx @@ -1,6 +1,6 @@ import type { ReactElement } from 'react'; import type { State, API, LeafEntry } from '@storybook/manager-api'; -import type { Addon_WrapperType, API_ViewMode, StoryId } from '@storybook/types'; +import type { Addon_BaseType, Addon_WrapperType, API_ViewMode, StoryId } from '@storybook/types'; export interface PreviewProps { api: API; @@ -13,13 +13,16 @@ export interface PreviewProps { showToolbar: boolean; }; id?: string; - path: string; - location: State['location']; queryParams: State['customQueryParams']; customCanvas?: CustomCanvasRenderer; description: string; baseUrl: string; withLoader: boolean; + tabs: Addon_BaseType[]; + tools: Addon_BaseType[]; + toolsExtra: Addon_BaseType[]; + tabId: string | undefined; + wrappers: Addon_WrapperType[]; } export interface ApplyWrappersProps { @@ -27,7 +30,6 @@ export interface ApplyWrappersProps { viewMode: State['viewMode']; id: string; storyId: StoryId; - active: boolean; } export type CustomCanvasRenderer = ( diff --git a/code/ui/manager/src/container/Preview.tsx b/code/ui/manager/src/container/Preview.tsx index 9fcf7a4a8819..91e78d45f0c4 100644 --- a/code/ui/manager/src/container/Preview.tsx +++ b/code/ui/manager/src/container/Preview.tsx @@ -1,10 +1,64 @@ import { global } from '@storybook/global'; +import type { Addon_BaseType, Addon_Collection, Addon_WrapperType } from '@storybook/types'; +import { Addon_TypesEnum } from '@storybook/types'; +import type { ComponentProps } from 'react'; import React from 'react'; -import type { Combo, StoriesHash } from '@storybook/manager-api'; +import memoizerific from 'memoizerific'; + +import type { State, StoriesHash } from '@storybook/manager-api'; import { Consumer } from '@storybook/manager-api'; -import { Preview } from '../components/preview/Preview'; +import { Preview, createCanvasTab, filterTabs } from '../components/preview/Preview'; +import { defaultWrappers } from '../components/preview/Wrappers'; +import { filterToolsSide, fullScreenTool } from '../components/preview/Toolbar'; +import { menuTool } from '../components/preview/tools/menu'; +import { remountTool } from '../components/preview/tools/remount'; +import { zoomTool } from '../components/preview/tools/zoom'; +import type { PreviewProps } from '../components/preview/utils/types'; +import { addonsTool } from '../components/preview/tools/addons'; +import { copyTool } from '../components/preview/tools/copy'; +import { ejectTool } from '../components/preview/tools/eject'; + +const defaultTabs = [createCanvasTab()]; +const defaultTools = [menuTool, remountTool, zoomTool]; +const defaultToolsExtra = [addonsTool, fullScreenTool, ejectTool, copyTool]; + +const emptyTabsList: Addon_BaseType[] = []; + +type FilterProps = [ + entry: PreviewProps['entry'], + viewMode: State['viewMode'], + location: State['location'], + path: State['path'], + tabId: string, +]; + +// memoization to return the same array every time, unless something relevant changes +const memoizedTabs = memoizerific(1)( + ( + _, + tabElements: Addon_Collection, + parameters: Record | undefined, + showTabs: boolean + ) => + showTabs + ? filterTabs([...defaultTabs, ...Object.values(tabElements)], parameters) + : emptyTabsList +); +const memoizedTools = memoizerific(1)( + (_, toolElements: Addon_Collection, filterProps: FilterProps) => + filterToolsSide([...defaultTools, ...Object.values(toolElements)], ...filterProps) +); +const memoizedExtra = memoizerific(1)( + (_, extraElements: Addon_Collection, filterProps: FilterProps) => + filterToolsSide([...defaultToolsExtra, ...Object.values(extraElements)], ...filterProps) +); + +const memoizedWrapper = memoizerific(1)((_, previewElements: Addon_Collection) => [ + ...defaultWrappers, + ...Object.values(previewElements), +]); const { PREVIEW_URL } = global; @@ -22,22 +76,59 @@ const getDescription = (item: Item) => { return item?.name ? `${item.name} â‹… Storybook` : 'Storybook'; }; -const mapper = ({ api, state }: Combo) => { +const mapper = ({ + api, + state, +}: Parameters['filter']>[0]): Omit< + ComponentProps, + 'withLoader' | 'id' +> => { const { layout, location, customQueryParams, storyId, refs, viewMode, path, refId } = state; const entry = api.getData(storyId, refId); + const tabsList = Object.values(api.getElements(Addon_TypesEnum.TAB)); + const wrapperList = Object.values(api.getElements(Addon_TypesEnum.PREVIEW)); + const toolsList = Object.values(api.getElements(Addon_TypesEnum.TOOL)); + const toolsExtraList = Object.values(api.getElements(Addon_TypesEnum.TOOLEXTRA)); + + const tabId = api.getQueryParam('tab'); + + const tools = memoizedTools(toolsList.length, api.getElements(Addon_TypesEnum.TOOL), [ + entry, + viewMode, + location, + path, + tabId, + ]) as Addon_BaseType[]; + const toolsExtra = memoizedExtra( + toolsExtraList.length, + api.getElements(Addon_TypesEnum.TOOLEXTRA), + [entry, viewMode, location, path, tabId] + ) as Addon_BaseType[]; + return { api, entry, options: layout, description: getDescription(entry), viewMode, - path, refs, storyId, baseUrl: PREVIEW_URL || 'iframe.html', queryParams: customQueryParams, - location, + tools: tools, + toolsExtra: toolsExtra, + tabs: memoizedTabs( + tabsList.length, + api.getElements(Addon_TypesEnum.TAB), + entry ? entry.parameters : undefined, + layout.showTabs + ) as Addon_BaseType[], + wrappers: memoizedWrapper( + wrapperList.length, + api.getElements(Addon_TypesEnum.PREVIEW) + ) as Addon_WrapperType[], + tabId: tabId, }; }; diff --git a/code/ui/manager/src/index.tsx b/code/ui/manager/src/index.tsx index d97a382f35c7..2af0ac508db7 100644 --- a/code/ui/manager/src/index.tsx +++ b/code/ui/manager/src/index.tsx @@ -72,7 +72,11 @@ const Main: FC<{ provider: Provider }> = ({ provider }) => { diff --git a/code/yarn.lock b/code/yarn.lock index 02847b06a441..ea86e67e973f 100644 --- a/code/yarn.lock +++ b/code/yarn.lock @@ -4270,19 +4270,14 @@ __metadata: languageName: node linkType: hard -"@playwright/test@npm:1.36.0": - version: 1.36.0 - resolution: "@playwright/test@npm:1.36.0" +"@playwright/test@npm:1.41.1": + version: 1.41.1 + resolution: "@playwright/test@npm:1.41.1" dependencies: - "@types/node": "npm:*" - fsevents: "npm:2.3.2" - playwright-core: "npm:1.36.0" - dependenciesMeta: - fsevents: - optional: true + playwright: "npm:1.41.1" bin: playwright: cli.js - checksum: 9d06764f55ae10569981d518838d588d820678a4e6a8a1bbfab78b7b4b4245cf055e94a0dea90fe2ecf1ce4559d06e02986757270e7c54a52d0bdf1963cb03e6 + checksum: 72bd5bb67c512027d214b9c54c2a22a469bd19d7809771e53a5bfdcc11330591e01579bb22f807d1ebbcdcea35d625e0fc9eb9791cebcc63bf55b82dd1cdefdd languageName: node linkType: hard @@ -6348,7 +6343,7 @@ __metadata: dependencies: "@chromaui/addon-visual-tests": "npm:^0.0.124" "@nx/workspace": "npm:17.0.2" - "@playwright/test": "npm:1.36.0" + "@playwright/test": "npm:1.41.1" "@storybook/addon-a11y": "workspace:*" "@storybook/addon-actions": "workspace:*" "@storybook/addon-backgrounds": "workspace:*" @@ -23035,23 +23030,27 @@ __metadata: languageName: node linkType: hard -"playwright-core@npm:1.36.0": - version: 1.36.0 - resolution: "playwright-core@npm:1.36.0" +"playwright-core@npm:1.41.1": + version: 1.41.1 + resolution: "playwright-core@npm:1.41.1" bin: playwright-core: cli.js - checksum: 5b5d32495e222ddd4351d1d8b116a25a7d93ea5f3439dceaceeb916d0abdce6d5b0b84f80df0b239168d55a100e7aecc9db4774ff20ba0210d8de027f258b544 + checksum: cdd91267ca23e3f65d519100e956859c70e3e9ca29e3fe00e700b457903129e41dfa17752f1ea37ad0a8a7c6330baf9f3be503e4cbfa3e8833e80a037f899aee languageName: node linkType: hard -"playwright@npm:1.36.0": - version: 1.36.0 - resolution: "playwright@npm:1.36.0" +"playwright@npm:1.41.1": + version: 1.41.1 + resolution: "playwright@npm:1.41.1" dependencies: - playwright-core: "npm:1.36.0" + fsevents: "npm:2.3.2" + playwright-core: "npm:1.41.1" + dependenciesMeta: + fsevents: + optional: true bin: playwright: cli.js - checksum: dc39eb6271b22901cb6219a8ecdd44736169edd789d2a8be5885ded5414ee2a9e2c73a19e24d55336ddbae6f9d69522b46f1fe92998740745f1cb78d58b6cb6e + checksum: 32d48c1f8ff881770a19c9245fb4191fc36b5e97ab5f48effa0b1cf5e83fa958f6fdd7e4268dd984aa306ac5fe9e4324510211910751fb52cebb9bae819d13ca languageName: node linkType: hard diff --git a/scripts/package.json b/scripts/package.json index 2d1cd5acc3c6..c016bd4d721b 100644 --- a/scripts/package.json +++ b/scripts/package.json @@ -149,8 +149,8 @@ "ora": "^5.4.1", "p-limit": "^3.1.0", "p-retry": "^5.1.2", - "playwright": "1.36.0", - "playwright-core": "1.36.0", + "playwright": "1.41.1", + "playwright-core": "1.41.1", "prettier": "^3.1.1", "pretty-bytes": "^6.1.0", "pretty-hrtime": "^1.0.0", diff --git a/scripts/utils/yarn.ts b/scripts/utils/yarn.ts index a2fb82e1fa6a..919e380f119c 100644 --- a/scripts/utils/yarn.ts +++ b/scripts/utils/yarn.ts @@ -25,9 +25,9 @@ export const addPackageResolutions = async ({ cwd, dryRun }: YarnOptions) => { ...storybookVersions, 'enhanced-resolve': '~5.10.0', // TODO, remove this // this is for our CI test, ensure we use the same version as docker image, it should match version specified in `./code/package.json` and `.circleci/config.yml` - playwright: '1.36.0', - 'playwright-core': '1.36.0', - '@playwright/test': '1.36.0', + playwright: '1.41.1', + 'playwright-core': '1.41.1', + '@playwright/test': '1.41.1', }; await writeJSON(packageJsonPath, packageJson, { spaces: 2 }); }; diff --git a/scripts/yarn.lock b/scripts/yarn.lock index f58785b1def6..5f6bc618e55a 100644 --- a/scripts/yarn.lock +++ b/scripts/yarn.lock @@ -2922,8 +2922,8 @@ __metadata: ora: "npm:^5.4.1" p-limit: "npm:^3.1.0" p-retry: "npm:^5.1.2" - playwright: "npm:1.36.0" - playwright-core: "npm:1.36.0" + playwright: "npm:1.41.1" + playwright-core: "npm:1.41.1" prettier: "npm:^3.1.1" pretty-bytes: "npm:^6.1.0" pretty-hrtime: "npm:^1.0.0" @@ -7856,6 +7856,16 @@ __metadata: languageName: node linkType: hard +"fsevents@npm:2.3.2": + version: 2.3.2 + resolution: "fsevents@npm:2.3.2" + dependencies: + node-gyp: "npm:latest" + checksum: be78a3efa3e181cda3cf7a4637cb527bcebb0bd0ea0440105a3bb45b86f9245b307dc10a2507e8f4498a7d4ec349d1910f4d73e4d4495b16103106e07eee735b + conditions: os=darwin + languageName: node + linkType: hard + "fsevents@npm:~2.3.2, fsevents@npm:~2.3.3": version: 2.3.3 resolution: "fsevents@npm:2.3.3" @@ -7866,6 +7876,15 @@ __metadata: languageName: node linkType: hard +"fsevents@patch:fsevents@npm%3A2.3.2#optional!builtin": + version: 2.3.2 + resolution: "fsevents@patch:fsevents@npm%3A2.3.2#optional!builtin::version=2.3.2&hash=df0bf1" + dependencies: + node-gyp: "npm:latest" + conditions: os=darwin + languageName: node + linkType: hard + "fsevents@patch:fsevents@npm%3A~2.3.2#optional!builtin, fsevents@patch:fsevents@npm%3A~2.3.3#optional!builtin": version: 2.3.3 resolution: "fsevents@patch:fsevents@npm%3A2.3.3#optional!builtin::version=2.3.3&hash=df0bf1" @@ -12437,23 +12456,27 @@ __metadata: languageName: node linkType: hard -"playwright-core@npm:1.36.0": - version: 1.36.0 - resolution: "playwright-core@npm:1.36.0" +"playwright-core@npm:1.41.1": + version: 1.41.1 + resolution: "playwright-core@npm:1.41.1" bin: playwright-core: cli.js - checksum: 5b5d32495e222ddd4351d1d8b116a25a7d93ea5f3439dceaceeb916d0abdce6d5b0b84f80df0b239168d55a100e7aecc9db4774ff20ba0210d8de027f258b544 + checksum: cdd91267ca23e3f65d519100e956859c70e3e9ca29e3fe00e700b457903129e41dfa17752f1ea37ad0a8a7c6330baf9f3be503e4cbfa3e8833e80a037f899aee languageName: node linkType: hard -"playwright@npm:1.36.0": - version: 1.36.0 - resolution: "playwright@npm:1.36.0" +"playwright@npm:1.41.1": + version: 1.41.1 + resolution: "playwright@npm:1.41.1" dependencies: - playwright-core: "npm:1.36.0" + fsevents: "npm:2.3.2" + playwright-core: "npm:1.41.1" + dependenciesMeta: + fsevents: + optional: true bin: playwright: cli.js - checksum: dc39eb6271b22901cb6219a8ecdd44736169edd789d2a8be5885ded5414ee2a9e2c73a19e24d55336ddbae6f9d69522b46f1fe92998740745f1cb78d58b6cb6e + checksum: 32d48c1f8ff881770a19c9245fb4191fc36b5e97ab5f48effa0b1cf5e83fa958f6fdd7e4268dd984aa306ac5fe9e4324510211910751fb52cebb9bae819d13ca languageName: node linkType: hard