UNPKG

storybook-addon-source-link

Version:

Add a button to the Storybook toolbar that opens the file containing the Story in an IDE like VSCode.

154 lines (149 loc) 5.21 kB
import { useStorybookApi, useChannel, addons, types } from 'storybook/manager-api'; import * as iconsModule from '@storybook/icons'; import { CopyIcon, CheckIcon, JumpToIcon } from '@storybook/icons'; import React2, { memo, useState, useCallback, useMemo } from 'react'; import { WithTooltip, TooltipLinkList, IconButton } from 'storybook/internal/components'; import { STORY_CHANGED } from 'storybook/internal/core-events'; import { styled } from 'storybook/theming'; // src/manager.ts // src/constants.ts var ADDON_ID = "storybook-addon-source-link"; var TOOL_ID = `${ADDON_ID}/tool`; var EVENTS = { REQUEST_RESOLVABLE_PARAM: `${ADDON_ID}/request-resolve`, RESPONSE_RESOLVABLE_PARAM: `${ADDON_ID}/response-resolve` }; var resolveLinks = async (context) => { const channel = addons.getChannel(); return new Promise((resolve) => { channel.on(EVENTS.RESPONSE_RESOLVABLE_PARAM, (resolvedParam) => { resolve(resolvedParam); }); channel.emit(EVENTS.REQUEST_RESOLVABLE_PARAM, context); }); }; var storybookIconMap = Object.fromEntries( Object.entries(iconsModule).filter((entry) => entry[0][0] === entry[0][0].toUpperCase()).map(([key, value]) => [key, value]) ); var isIconName = (name) => Object.keys(storybookIconMap).includes(name); var StorybookIcon = ({ name }) => { const IconComponent = storybookIconMap[name]; if (!IconComponent) return null; return /* @__PURE__ */ React2.createElement(IconComponent, null); }; // src/manager/Tooltip.tsx var ColoredCopyIcon = styled(CopyIcon)` fill: ${({ theme }) => theme.color.dark}; `; var ColoredCheckIcon = styled(CheckIcon)` fill: ${({ theme }) => theme.color.dark}; `; var checkIsStaticBuild = () => { try { return window.CONFIG_TYPE !== "DEVELOPMENT"; } catch { console.warn( "[storybook-addon-source-link] window.CONFIG_TYPE is not defined. The value of isStaticBuild may be incorrect." ); return false; } }; var Tool = memo(function MyAddonSelector() { const rootPath = process.env.SOURCE_LINK_PROJECT_ROOT_PATH ?? ""; const isStaticBuild = checkIsStaticBuild(); const api = useStorybookApi(); const storyData = api.getCurrentStoryData(); const [linksData, setLinksData] = useState(); const [copyClickedLink, setCopyClickedLink] = useState(); useChannel({ [STORY_CHANGED]: () => { setLinksData(void 0); } }); const onOpenTooltip = useCallback(() => { setCopyClickedLink(void 0); if (!storyData) return; if (linksData) return; resolveLinks({ rootPath, isStaticBuild, type: storyData.type, importPath: storyData.importPath, id: storyData.id, title: storyData.title, name: storyData.name, tags: storyData.tags }).then((link) => { setLinksData(link); }); }, [rootPath, isStaticBuild, storyData, linksData]); const links = useMemo(() => { return linksData?.map((item) => { if (item.type === "editor") { return { id: item.id, title: item.label, onClick: () => { api.openInEditor({ file: item.href }); }, icon: item.icon && isIconName(item.icon) && /* @__PURE__ */ React2.createElement(StorybookIcon, { name: item.icon }), order: item.order ?? 0 }; } if (item.type === "copy") { const clicked = !!copyClickedLink; return { id: item.id, title: item.label, onClick: () => { navigator.clipboard.writeText(item.href); if (!clicked) { setCopyClickedLink(item.id); } }, icon: item.icon && isIconName(item.icon) && /* @__PURE__ */ React2.createElement(StorybookIcon, { name: item.icon }), right: clicked ? /* @__PURE__ */ React2.createElement(ColoredCheckIcon, null) : /* @__PURE__ */ React2.createElement(ColoredCopyIcon, null), order: item.order ?? 0 }; } return { id: item.id, title: item.label, href: item.href, target: (item.type ?? "linkBlank") === "linkBlank" ? "_blank" : void 0, icon: item.icon && isIconName(item.icon) && /* @__PURE__ */ React2.createElement(StorybookIcon, { name: item.icon }), order: item.order ?? 0 }; }).sort((a, b) => { if (a.order === b.order) { return a.title.localeCompare(b.title); } return a.order - b.order; }); }, [linksData, copyClickedLink]); return /* @__PURE__ */ React2.createElement( WithTooltip, { placement: "top", closeOnOutsideClick: true, onVisibleChange: (state) => { if (state) { onOpenTooltip(); } }, tooltip: () => /* @__PURE__ */ React2.createElement(TooltipLinkList, { links: links ?? [] }) }, /* @__PURE__ */ React2.createElement(IconButton, { key: "open-source-file", title: "Open source file" }, /* @__PURE__ */ React2.createElement(JumpToIcon, null)) ); }); // src/manager.ts addons.register(ADDON_ID, () => { addons.add(TOOL_ID, { type: types.TOOL, title: "Source Link", match: ({ viewMode }) => !!viewMode?.match(/^(story|docs)$/), render: Tool }); });