Skip to content

Commit

Permalink
Merge pull request #6 from elecdeer/feat/link_type
Browse files Browse the repository at this point in the history
 Add link type and support for clipboard-only links
  • Loading branch information
elecdeer authored Jul 20, 2024
2 parents 3740e0e + 873c6c2 commit 6cdc71c
Show file tree
Hide file tree
Showing 5 changed files with 104 additions and 29 deletions.
5 changes: 5 additions & 0 deletions .changeset/warm-planets-try.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"storybook-addon-source-link": minor
---

Add link type and support for clipboard-only links
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,10 @@ If `undefined` is returned, the link will not be added.
- `label`: The label of the link.
- `href`: The URL of the link.
- `icon`: (Optional) The icon name in [@storybook/icons](https://main--64b56e737c0aeefed9d5e675.chromatic.com/?path=/docs/introduction--docs)
- `type`: (Optional) The type of the link.
- `"link"`: The link will be opened in the same tab.
- `"linkBlank"`: (default) The link will be opened in a new tab. Added target="\_blank" to the link.
- `"copy"`: The link will be copied to the clipboard.
- `order`: (Optional) When order is specified, it will be sorted in ascending order. The default value is `0`.

### Preset settings provided by the addon
Expand Down
91 changes: 62 additions & 29 deletions packages/addon-source-link/src/manager/Tooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,39 +5,50 @@ import {
} from "@storybook/core/components";
import { STORY_CHANGED } from "@storybook/core/core-events";
import { useChannel, useStorybookApi } from "@storybook/core/manager-api";
import { styled } from "@storybook/core/theming";
import type { API_LeafEntry } from "@storybook/core/types";
import { JumpToIcon } from "@storybook/icons";
import React, { memo, useCallback, useState, type ReactNode } from "react";
import { CheckIcon, CopyIcon, JumpToIcon } from "@storybook/icons";
import React, {
memo,
useCallback,
useMemo,
useState,
type ReactNode,
} from "react";
import type { LinkEntry } from "../types";
import { StorybookIcon, isIconName } from "./StorybookIcon";
import { resolveLinks } from "./resolveParameter";

const ColoredCopyIcon = styled(CopyIcon)`
fill: ${({ theme }) => theme.color.dark};
`;

const ColoredCheckIcon = styled(CheckIcon)`
fill: ${({ theme }) => theme.color.dark};
`;

export const Tool = memo(function MyAddonSelector() {
const rootPath = process.env.SOURCE_LINK_PROJECT_ROOT_PATH ?? "";
const isStaticBuild = process.env.NODE_ENV === "production";

const api = useStorybookApi();
const storyData = api.getCurrentStoryData() as API_LeafEntry | undefined;

const [links, setLinks] = useState<
| {
id: string;
title: string;
href: string;
target: string;
icon?: ReactNode;
}[]
| undefined
const [linksData, setLinksData] = useState<
(LinkEntry & { id: string })[] | undefined
>();
const [copyClickedLink, setCopyClickedLink] = useState<string>();

useChannel({
[STORY_CHANGED]: () => {
setLinks(undefined);
setLinksData(undefined);
},
});

const onOpenTooltip = useCallback(() => {
setCopyClickedLink(undefined);
if (!storyData) return;
if (links) return;
if (linksData) return;

resolveLinks({
rootPath,
Expand All @@ -48,37 +59,59 @@ export const Tool = memo(function MyAddonSelector() {
title: storyData.title,
name: storyData.name,
tags: storyData.tags,
}).then((links) => {
const sortedLinks = links
.map((item) => {
}).then((link) => {
setLinksData(link);
});
}, [rootPath, isStaticBuild, storyData, linksData]);

const links = useMemo(() => {
return linksData
?.map((item) => {
if (item.type === "copy") {
const clicked = !!copyClickedLink;
return {
id: item.id,
title: item.label,
href: item.href,
target: "_blank",
onClick: () => {
navigator.clipboard.writeText(item.href);
if (!clicked) {
setCopyClickedLink(item.id);
}
},
icon: item.icon && isIconName(item.icon) && (
<StorybookIcon name={item.icon} />
),
right: clicked ? <ColoredCheckIcon /> : <ColoredCopyIcon />,
order: item.order ?? 0,
};
})
.sort((a, b) => {
if (a.order === b.order) {
return a.title.localeCompare(b.title);
}
return a.order - b.order;
});
}

setLinks(sortedLinks);
});
}, [rootPath, isStaticBuild, storyData, links]);
return {
id: item.id,
title: item.label,
href: item.href,
target:
(item.type ?? "linkBlank") === "linkBlank" ? "_blank" : undefined,
icon: item.icon && isIconName(item.icon) && (
<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 (
<WithTooltip
placement="top"
closeOnOutsideClick
onVisibleChange={(state) => {
if (state && !links) {
if (state) {
onOpenTooltip();
}
}}
Expand Down
23 changes: 23 additions & 0 deletions packages/addon-source-link/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,32 @@ export type Resolvable<T> = ((context: ResolveContext) => T) | T;

export type LinkEntry =
| {
/**
* The label of the link.
*/
label: string;
/**
* The URL of the link.
*/
href: string;
/**
* The icon name in [@storybook/icons](https://main--64b56e737c0aeefed9d5e675.chromatic.com/?path=/docs/introduction--docs)
*/
icon?: IconName | undefined;
/**
* When order is specified, it will be sorted in ascending order. The default value is `0`.
*/
order?: number | undefined;

/**
* The type of the link.
*
* - `"link"`: The link will be opened in the same tab.
* - `"linkBlank"`: The link will be opened in a new tab. Added target="_blank" to the link.
* - `"copy"`: The link will be copied to the clipboard.
*
* @default "linkBlank"
*/
type?: "link" | "linkBlank" | "copy" | undefined;
}
| undefined;
10 changes: 10 additions & 0 deletions packages/demo/.storybook/preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,16 @@ const preview: Preview = {
icon: "GithubIcon",
};
},
"story-github-copy": ({ importPath, rootPath }) => {
if (!rootPath) return undefined;
const href = `https://github.com/elecdeer/storybook-addon-source-link/blob/-/packages/demo${importPath.replace(/^\./, "")}`;
return {
label: importPath,
href,
type: "copy",
icon: "GithubIcon",
};
},
},
} satisfies SourceLinkParameter,
},
Expand Down

0 comments on commit 6cdc71c

Please sign in to comment.