diff --git a/.storybook/preview.js b/.storybook/preview.js index d130f73..dd717c0 100644 --- a/.storybook/preview.js +++ b/.storybook/preview.js @@ -1,9 +1,9 @@ const preview = { - parameters: { + initialGlobals: { marker: { project: '60f162459a86003bf9d741b3', } - } + }, }; export default preview; diff --git a/README.md b/README.md index a77826a..317195e 100644 --- a/README.md +++ b/README.md @@ -22,15 +22,15 @@ export default { } ``` -Then create a file called `preview.js` in the same folder and add your [Marker project ID](https://marker.io/blog/integrate-web-app-browser-sdk) as a [parameter](https://storybook.js.org/docs/react/writing-stories/parameters). +Then create a file called `preview.js` in the same folder and add your [Marker project ID](https://marker.io/blog/integrate-web-app-browser-sdk) in the [initalGlobals](https://storybook.js.org/docs/essentials/toolbars-and-globals). ```js export default { - parameters: { + initialGlobals: { marker: { project: 'abcd1234567890', // Your unique project ID - } - } + }, + }, } ``` @@ -39,11 +39,13 @@ Only `project` is required, the [rest of the marker widget params](https://githu Additionally, the `mode` option of [the browser SDK `capture` method](https://github.com/marker-io/browser-sdk?tab=readme-ov-file#widgetcapturemode) can be added to this config: ```js -export const parameters = { - marker: { - project: 'abcd1234567890', // <- Your unique project ID - mode: 'fullscreen', // fullscreen | advanced - } +export default { + initialGlobals: { + marker: { + project: 'abcd1234567890', // <- Your unique project ID + mode: 'fullscreen', // fullscreen | advanced + }, + }, } ``` diff --git a/package.json b/package.json index 31701d4..927d4a9 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ ], "scripts": { "prepare": "tsup", - "storybook": "storybook dev -p 6006", + "storybook": "npm run prepare && storybook dev -p 6006", "build-storybook": "storybook build", "release": "semantic-release" }, diff --git a/src/FeedbackButton.jsx b/src/FeedbackButton.jsx index 7f3eed2..ede4a55 100644 --- a/src/FeedbackButton.jsx +++ b/src/FeedbackButton.jsx @@ -1,10 +1,12 @@ +import markerSDK from '@marker.io/browser'; import { IconButton } from '@storybook/components'; import { CommentIcon } from '@storybook/icons'; -import { useChannel } from '@storybook/manager-api'; +import { useChannel, useGlobals } from '@storybook/manager-api'; import { styled } from '@storybook/theming'; -import React, { useCallback, useState } from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; import { EVENTS, TOOL_ID } from './constants'; +import { hideDefaultMarkerButton } from './hideDefaultMarkerButton'; const IconButtonWithLabel = styled(IconButton)(() => ({ display: 'inline-flex', @@ -27,17 +29,46 @@ const IconButtonLabel = styled.div(({ theme }) => ({ export default function FeedbackButton() { const [markerLoaded, setMarkerLoaded] = useState(false); + const [globals] = useGlobals(); + const { project, mode, ...config } = globals.marker ?? {}; const emit = useChannel({ + // The loaded event will fire when the marker decorator loads [EVENTS.LOADED]: () => { + if (window.Marker) { + // Unload the manager version of marker + window.Marker.unload(); + } + setMarkerLoaded(true); }, + [EVENTS.CAPTURE]: () => { + window.Marker?.capture(mode)?.then(() => { + hideDefaultMarkerButton(); // This sometimes reappears after capturing + }); + }, }); const handleSendFeedback = useCallback(() => { emit(EVENTS.CAPTURE); }, [emit]); + // If the decorator has not loaded within 3 seconds fallback to loading it on the manager. + // Screenshots may appear unstyled, but it's better than no feedback button displaying. + // This might happen on mdx docs stories where no decorators aren't called. + useEffect(() => { + clearTimeout(window.markerTimer); + + if (project && !markerLoaded && !window.Marker) { + window.markerTimer = setTimeout(() => { + markerSDK.loadWidget({ project, ...config }).then(() => { + hideDefaultMarkerButton(); + setMarkerLoaded(true); + }); + }, 3000); + } + }, [project, markerLoaded]); + return markerLoaded ? ( diff --git a/src/hideDefaultMarkerButton.js b/src/hideDefaultMarkerButton.js new file mode 100644 index 0000000..b5802e2 --- /dev/null +++ b/src/hideDefaultMarkerButton.js @@ -0,0 +1,6 @@ +export const hideDefaultMarkerButton = () => { + const markerBtns = [ + ...document.querySelectorAll('.marker-app #feedback-button'), + ]; + markerBtns.forEach((markerBtn) => (markerBtn.style.display = 'none')); +}; diff --git a/src/withMarker.js b/src/withMarker.js index e9f1c72..64e15c0 100644 --- a/src/withMarker.js +++ b/src/withMarker.js @@ -1,40 +1,30 @@ /* eslint-disable react-hooks/rules-of-hooks */ import markerSDK from '@marker.io/browser'; -import { useChannel, useParameter } from '@storybook/preview-api'; +import { useChannel, useGlobals } from '@storybook/preview-api'; import { EVENTS } from './constants'; - -const hideDefaultMarkerButton = () => { - const markerBtns = [ - ...document.querySelectorAll('.marker-app #feedback-button'), - ]; - markerBtns.forEach((markerBtn) => (markerBtn.style.display = 'none')); -}; +import { hideDefaultMarkerButton } from './hideDefaultMarkerButton'; export const withMarker = (storyFn) => { - const { destination, project, mode, ...config } = useParameter('marker', {}); + const [globals] = useGlobals(); + const { project, mode, ...config } = globals.marker ?? {}; const emit = useChannel({ [EVENTS.CAPTURE]: () => { - window.Marker?.capture(mode).then(() => { + window.Marker?.capture(mode)?.then(() => { hideDefaultMarkerButton(); // This sometimes reappears after capturing }); }, }); - if ((!destination && !project) || window.Marker) { + if (!project || window.Marker) { return storyFn(); } - markerSDK - .loadWidget({ - project: project ?? destination, - ...config, - }) - .then(() => { - hideDefaultMarkerButton(); - emit(EVENTS.LOADED); - }); + markerSDK.loadWidget({ project, ...config }).then(() => { + hideDefaultMarkerButton(); + emit(EVENTS.LOADED); + }); return storyFn(); };