Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 77 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ Display Github permalinks as codeblocks.

Display Github issue links.

Display Github PR links.

![screenshot of the tool in action - dark mode ](./screenshot-permalink-dark.png)
![screenshot of the tool in action - light mode ](./screenshot-permalink-light.png)
![screenshot of the tool in action - dark mode ](./screenshot-issuelink-dark.png)
Expand All @@ -27,9 +29,9 @@ This package is compatible with Next 13+ and the components can be used as RSCs

Three variants of each component are exported

- GithubPermalink/GithubIssueLink - Client component - It fetches the data as on the client in a useEffect. ie. Data won't be retrieved until application has loaded in user's browser.
- GithubPermalinkBase/GithubIssueLinkBase - this is the base component - it does no data fetching on its own.
- GithubPermalinkRsc/GithubIssueLinkRsc - This is an RSC.
- GithubPermalink/GithubIssueLink/GithubPRLink - Client component - It fetches the data as on the client in a useEffect. ie. Data won't be retrieved until application has loaded in user's browser.
- GithubPermalinkBase/GithubIssueLinkBase/GithubPRLinkBase - this is the base component - it does no data fetching on its own.
- GithubPermalinkRsc/GithubIssueLinkRsc/GithubPRLinkRsc - This is an RSC.



Expand Down Expand Up @@ -112,6 +114,72 @@ export function MyApp() {
}
```

## Github PR Link

### Usage
```jsx
import { GithubPRLink } from 'react-github-permalink';
import "react-github-permalink/dist/github-permalink.css"; // Or provide your own styles

export function MyApp() {
return <GithubPRLink prLink='https://github.com/facebook/react/pull/24652' />,
}
```

PR Link also has an inline variant:

```jsx
export function MyApp() {
return <GithubPRLink prLink='https://github.com/facebook/react/pull/24652' variant="inline"/>,
}
```

### PR Link with custom data

```jsx
import { GithubPRLinkBase } from 'react-github-permalink';
import "react-github-permalink/dist/github-permalink.css"; // Or provide your own styles

export function MyApp() {
return <GithubPRLinkBase
prLink="https://github.com/facebook/react/pull/24652"
data={{
prTitle: "Add concurrent features to React",
prNumber: "24652",
prState: "closed",
owner: "facebook",
repo: "react",
status: "ok",
isDraft: false,
merged: true,
mergeable: null,
reactions: {
"+1": 42,
"-1": 0,
confused: 0,
eyes: 2,
heart: 8,
hooray: 15,
laugh: 0,
rocket: 5,
total_count: 72
}
}}
/>
}
```

### PR Link RSC

```jsx
import { GithubPRLinkRsc } from 'react-github-permalink/dist/rsc';
import "react-github-permalink/dist/github-permalink.css"; // Or provide your own styles

export function MyApp() {
return <GithubPRLinkRsc prLink="https://github.com/facebook/react/pull/24652"/>
}
```

## Rate Limits and Authentication

By default the components make unauthenticated requests against Github's API. The rate limit for such requests is 60/hour and only publicly visible repositories are available.
Expand All @@ -126,6 +194,7 @@ The global configuration object has this signature
type BaseConfiguration = {
getDataFn: (permalink: string, githubToken?: string | undefined, onError?: ((err: unknown) => void) | undefined) => Promise<GithubPermalinkDataResponse>;
getIssueFn: (issueLink: string, githubToken?: string | undefined, onError?: ((err: unknown) => void) | undefined) => Promise<GithubIssueLinkDataResponse>;
getPRFn: (prLink: string, githubToken?: string | undefined, onError?: ((err: unknown) => void) | undefined) => Promise<GithubPRLinkDataResponse>;
githubToken: string | undefined;
onError: ((e: unknown) => void) | undefined;
}
Expand All @@ -136,7 +205,7 @@ type BaseConfiguration = {
Client components are configured via context provider:

```tsx
import { GithubPermalink, GithubIssueLink GithubPermalinkProvider, } from 'react-github-permalink';
import { GithubPermalink, GithubIssueLink, GithubPRLink, GithubPermalinkProvider } from 'react-github-permalink';
import "react-github-permalink/dist/github-permalink.css";

export function MyApp() {
Expand All @@ -147,6 +216,9 @@ export function MyApp() {
getIssueFn={(issueLink: string) => {
// Your implementation to retrieve issue links here
}}
getPRFn={(prLink: string) => {
// Your implementation to retrieve PR links here
}}

// Don't put a put a github token into the context provider in production! It will visible for all the world to see!
// Instead you will need to expose a data fetching function on the backend to do it for you
Expand All @@ -158,6 +230,7 @@ export function MyApp() {
>
<GithubPermalink permalink="https://github.com/dwjohnston/react-github-permalink/blob/5b15aa07e60af4e317086f391b28cadf9aae8e1b/sample_files/sample1.go#L1-L5"/>
<GithubIssueLink issueLink='https://github.com/dwjohnston/react-github-permalink/issues/2' />
<GithubPRLink prLink='https://github.com/facebook/react/pull/24652' />
</GithubPermalinkProvider>
}
```
Expand Down
43 changes: 43 additions & 0 deletions src/library/GithubPRLink/GithubPRLink.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import type { Meta, StoryObj } from "@storybook/react";

import { GithubPRLink } from "./GithubPRLink";
import {
GithubPermalinkContext,
GithubPermalinkProvider,
} from "../config/GithubPermalinkContext";
import "../GithubPermalink/github-permalink.css";

const meta: Meta<typeof GithubPRLink> = {
component: GithubPRLink,
};

export default meta;

type Story = StoryObj<typeof GithubPRLink>;

/*
*👇 Render functions are a framework specific feature to allow you control on how the component renders.
* See https://storybook.js.org/docs/react/api/csf
* to learn how to use render functions.
*/
export const Primary: Story = {
render: () => (
<GithubPRLink prLink="https://github.com/facebook/react/pull/24652" />
),
};

export const WithBackground: Story = {
render: () => (
<div style={{ backgroundColor: "pink", padding: "1em" }}>
<GithubPRLink prLink="https://github.com/facebook/react/pull/24652" />
</div>
),
};

export const WithToken: Story = {
render: () => (
<GithubPermalinkProvider githubToken={process.env.STORYBOOK_GITHUB_TOKEN}>
<GithubPRLink prLink="https://github.com/facebook/react/pull/24652" />
</GithubPermalinkProvider>
),
};
33 changes: 33 additions & 0 deletions src/library/GithubPRLink/GithubPRLink.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
"use client"

import { useContext, useEffect, useState } from "react";

import { GithubPermalinkContext, GithubPRLinkDataResponse } from "../config/GithubPermalinkContext";
import { GithubPRLinkBase, GithubPRLinkBaseProps } from "./GithubPRLinkBase";

type GithubPRLinkProps = Omit<GithubPRLinkBaseProps, "data">;

export function GithubPRLink(props: GithubPRLinkProps) {

const { prLink } = props;
const [data, setData] = useState(null as null | GithubPRLinkDataResponse)
const { getPRFn, githubToken, onError} = useContext(GithubPermalinkContext);
const [isLoading, setIsLoading] = useState(true);

useEffect(() => {
getPRFn(prLink, githubToken, onError).then((v) => {
setIsLoading(false);
setData(v);
})
}, [getPRFn, githubToken, prLink, onError])

if (isLoading) {
return null;
}
if (!data) {
throw new Error("Loading is complete, but no data was returned.")
}

return <GithubPRLinkBase {...props} data={data}/>

}
103 changes: 103 additions & 0 deletions src/library/GithubPRLink/GithubPRLinkBase.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import type { Meta, StoryObj } from "@storybook/react";

import { GithubPRLinkBase } from "./GithubPRLinkBase";
import "../GithubPermalink/github-permalink.css";

const meta: Meta<typeof GithubPRLinkBase> = {
component: GithubPRLinkBase,
};

export default meta;

type Story = StoryObj<typeof GithubPRLinkBase>;

const sampleData = {
prTitle: "Add concurrent features to React",
prNumber: "24652",
prState: "closed" as const,
owner: "facebook",
repo: "react",
status: "ok" as const,
isDraft: false,
merged: true,
mergeable: null,
reactions: {
"+1": 42,
"-1": 0,
confused: 0,
eyes: 2,
heart: 8,
hooray: 15,
laugh: 0,
rocket: 5,
total_count: 72
}
};

const draftData = {
...sampleData,
prTitle: "WIP: New feature implementation",
prNumber: "12345",
prState: "open" as const,
isDraft: true,
merged: false,
};

const openData = {
...sampleData,
prTitle: "Fix bug in component rendering",
prNumber: "54321",
prState: "open" as const,
isDraft: false,
merged: false,
};

export const Primary: Story = {
render: () => (
<GithubPRLinkBase
prLink="https://github.com/facebook/react/pull/24652"
data={sampleData}
/>
),
};

export const Draft: Story = {
render: () => (
<GithubPRLinkBase
prLink="https://github.com/facebook/react/pull/12345"
data={draftData}
/>
),
};

export const Open: Story = {
render: () => (
<GithubPRLinkBase
prLink="https://github.com/facebook/react/pull/54321"
data={openData}
/>
),
};

export const Inline: Story = {
render: () => (
<div>
<p>Here's an inline PR link: <GithubPRLinkBase
prLink="https://github.com/facebook/react/pull/24652"
data={sampleData}
variant="inline"
/></p>
</div>
),
};

export const WithBackground: Story = {
render: () => (
<div style={{ backgroundColor: "pink", padding: "1em" }}>
<GithubPRLinkBase
prLink="https://github.com/facebook/react/pull/24652"
data={sampleData}
/>
</div>
),
};
Loading