diff --git a/README.md b/README.md
index fb1b2b5..1e22287 100644
--- a/README.md
+++ b/README.md
@@ -4,6 +4,74 @@ Display Github permalinks as codeblocks.
Display Github issue links.
+Display browser compatibility tables from caniuse.com.
+
+
+
+
+
+
+
+
+## CaniuseLink Component
+
+The CaniuseLink component displays live browser compatibility tables for web features using data from caniuse.com. It shows support across different browsers and versions with color-coded indicators.
+
+### Basic Usage
+
+```jsx
+import { CaniuseLink } from 'react-github-permalink';
+import "react-github-permalink/dist/github-permalink.css";
+
+export function MyApp() {
+ return
+}
+```
+
+### Inline Usage
+
+```jsx
+
This layout uses for positioning.
+```
+
+### With Custom Data
+
+```jsx
+import { CaniuseLinkBase } from 'react-github-permalink';
+
+const customData = {
+ title: "CSS Flexible Box Layout Module",
+ description: "Method of positioning elements...",
+ stats: {
+ chrome: { "89": "y", "90": "y" },
+ firefox: { "84": "y", "85": "y" },
+ safari: { "14.0": "y", "14.1": "y" },
+ edge: { "89": "y", "90": "y" },
+ ie: { "8": "n", "9": "n", "10": "a x", "11": "a" }
+ },
+ status: "ok"
+};
+
+export function MyApp() {
+ return
+}
+```
+
+### RSC Usage
+
+```jsx
+import { CaniuseLinkRsc } from 'react-github-permalink/dist/rsc';
+
+export function MyApp() {
+ return
+}
+```
+
+The component fetches live data from the caniuse GitHub repository, ensuring up-to-date compatibility information. Support levels are color-coded:
+- **Green**: Fully supported
+- **Yellow**: Partial support
+- **Red**: Not supported
+



@@ -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;
getIssueFn: (issueLink: string, githubToken?: string | undefined, onError?: ((err: unknown) => void) | undefined) => Promise;
+ getCaniuseFn: (feature: string, githubToken?: string | undefined, onError?: ((err: unknown) => void) | undefined) => Promise;
githubToken: string | undefined;
onError: ((e: unknown) => void) | undefined;
}
@@ -136,21 +205,24 @@ type BaseConfiguration = {
Client components are configured via context provider:
```tsx
-import { GithubPermalink, GithubIssueLink GithubPermalinkProvider, } from 'react-github-permalink';
+import { GithubPermalink, GithubIssueLink, CaniuseLink, GithubPermalinkProvider } from 'react-github-permalink';
import "react-github-permalink/dist/github-permalink.css";
export function MyApp() {
return {
+ getDataFn={(permalink: string) => {
// Your implementation to retrieve permalinks here
}}
getIssueFn={(issueLink: string) => {
// Your implementation to retrieve issue links here
}}
+ getCaniuseFn={(feature: string) => {
+ // Your implementation to retrieve caniuse data here
+ }}
- // Don't put a put a github token into the context provider in production! It will visible for all the world to see!
+ // Don't put a github token into the context provider in production! It will be 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
- githubToken={process.env.NODE_ENV='development' && process.env.MY_GITHUB_TOKEN}
+ githubToken={process.env.NODE_ENV === 'development' && process.env.MY_GITHUB_TOKEN}
onError={(err) => {
Sentry.captureException(err);
@@ -158,6 +230,7 @@ export function MyApp() {
>
+
}
```
diff --git a/src/app/page.tsx b/src/app/page.tsx
index 77ec7d0..d5388d8 100644
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -1,12 +1,49 @@
+import { CaniuseLink } from "../library/CaniuseLink/CaniuseLink";
+import { CaniuseLinkBase } from "../library/CaniuseLink/CaniuseLinkBase";
+import { GithubPermalinkProvider } from "../library/config/GithubPermalinkContext";
+import "../library/GithubPermalink/github-permalink.css";
+
+// Mock data for demonstration
+const mockFlexboxData = {
+ title: "CSS Flexible Box Layout Module",
+ description: "Method of positioning elements in horizontal or vertical stacks. Support includes all properties prefixed with `flex`, as well as `display: flex`, `display: inline-flex`, `align-content`, `align-items`, `align-self`, `justify-content` and `order`.",
+ stats: {
+ chrome: { "89": "y", "90": "y", "91": "y", "92": "y", "93": "y" },
+ firefox: { "84": "y", "85": "y", "86": "y", "87": "y", "88": "y" },
+ safari: { "14.0": "y", "14.1": "y", "15.0": "y", "15.1": "y", "15.2": "y" },
+ edge: { "89": "y", "90": "y", "91": "y", "92": "y", "93": "y" },
+ ie: { "8": "n", "9": "n", "10": "a x", "11": "a" }
+ },
+ status: "ok" as const
+};
+
export default function Home() {
return (
-
+
React Github Permalink
-
-
- See the
Storybook
+
See the Storybook
+
+
CaniuseLink Demo
+
+
+
Live API Example
+
+
+
+
+
Mock Data Example
+
+
+
+
+
Inline Usage
+
+ This layout uses for positioning elements.
+
+
+
);
diff --git a/src/library/CaniuseLink/CaniuseLink.stories.tsx b/src/library/CaniuseLink/CaniuseLink.stories.tsx
new file mode 100644
index 0000000..498f141
--- /dev/null
+++ b/src/library/CaniuseLink/CaniuseLink.stories.tsx
@@ -0,0 +1,42 @@
+import type { Meta, StoryObj } from "@storybook/react";
+import { CaniuseLink } from "./CaniuseLink";
+import {
+ GithubPermalinkProvider,
+} from "../config/GithubPermalinkContext";
+import "../GithubPermalink/github-permalink.css";
+
+const meta: Meta
= {
+ component: CaniuseLink,
+};
+
+export default meta;
+
+type Story = StoryObj;
+
+export const BlockSuccess: Story = {
+ render: () => (
+
+
+
+ ),
+};
+
+export const InlineSuccess: Story = {
+ render: () => (
+
+
+ This example uses{" "}
+
+ {" "}for layout.
+
+
+ ),
+};
+
+export const BlockError: Story = {
+ render: () => (
+
+
+
+ ),
+};
\ No newline at end of file
diff --git a/src/library/CaniuseLink/CaniuseLink.test.ts b/src/library/CaniuseLink/CaniuseLink.test.ts
new file mode 100644
index 0000000..a7d6058
--- /dev/null
+++ b/src/library/CaniuseLink/CaniuseLink.test.ts
@@ -0,0 +1,71 @@
+import { expect, test, describe, vi } from 'vitest'
+import { defaultGetCaniuseFn } from '../config/defaultFunctions'
+
+// Mock fetch for testing
+global.fetch = vi.fn()
+
+describe('defaultGetCaniuseFn', () => {
+ test('should handle successful response', async () => {
+ const mockResponse = {
+ title: 'CSS Flexible Box Layout Module',
+ description: 'Method of positioning elements in horizontal or vertical stacks.',
+ stats: {
+ chrome: {
+ '89': 'y',
+ '90': 'y'
+ },
+ firefox: {
+ '84': 'y',
+ '85': 'y'
+ }
+ }
+ }
+
+ vi.mocked(fetch).mockResolvedValueOnce({
+ ok: true,
+ json: async () => mockResponse,
+ } as Response)
+
+ const result = await defaultGetCaniuseFn('flexbox')
+
+ expect(result).toEqual({
+ title: 'CSS Flexible Box Layout Module',
+ description: 'Method of positioning elements in horizontal or vertical stacks.',
+ stats: {
+ chrome: {
+ '89': 'y',
+ '90': 'y'
+ },
+ firefox: {
+ '84': 'y',
+ '85': 'y'
+ }
+ },
+ status: 'ok'
+ })
+ })
+
+ test('should handle 404 response', async () => {
+ vi.mocked(fetch).mockResolvedValueOnce({
+ ok: false,
+ status: 404,
+ headers: new Headers(),
+ } as Response)
+
+ const result = await defaultGetCaniuseFn('non-existent-feature')
+
+ expect(result).toEqual({
+ status: '404'
+ })
+ })
+
+ test('should handle network error', async () => {
+ vi.mocked(fetch).mockRejectedValueOnce(new Error('Network error'))
+
+ const result = await defaultGetCaniuseFn('flexbox')
+
+ expect(result).toEqual({
+ status: 'other-error'
+ })
+ })
+})
\ No newline at end of file
diff --git a/src/library/CaniuseLink/CaniuseLink.tsx b/src/library/CaniuseLink/CaniuseLink.tsx
new file mode 100644
index 0000000..027e9cf
--- /dev/null
+++ b/src/library/CaniuseLink/CaniuseLink.tsx
@@ -0,0 +1,30 @@
+"use client"
+
+import { useContext, useEffect, useState } from "react";
+import { GithubPermalinkContext, CaniuseLinkDataResponse } from "../config/GithubPermalinkContext";
+import { CaniuseLinkBase, CaniuseLinkBaseProps } from "./CaniuseLinkBase";
+
+type CaniuseLinkProps = Omit;
+
+export function CaniuseLink(props: CaniuseLinkProps) {
+ const { feature } = props;
+ const [data, setData] = useState(null as null | CaniuseLinkDataResponse);
+ const { getCaniuseFn, githubToken, onError } = useContext(GithubPermalinkContext);
+ const [isLoading, setIsLoading] = useState(true);
+
+ useEffect(() => {
+ getCaniuseFn(feature, githubToken, onError).then((v) => {
+ setIsLoading(false);
+ setData(v);
+ });
+ }, [getCaniuseFn, githubToken, feature, onError]);
+
+ if (isLoading) {
+ return null;
+ }
+ if (!data) {
+ throw new Error("Loading is complete, but no data was returned.");
+ }
+
+ return ;
+}
\ No newline at end of file
diff --git a/src/library/CaniuseLink/CaniuseLinkBase.stories.tsx b/src/library/CaniuseLink/CaniuseLinkBase.stories.tsx
new file mode 100644
index 0000000..ba8ab95
--- /dev/null
+++ b/src/library/CaniuseLink/CaniuseLinkBase.stories.tsx
@@ -0,0 +1,103 @@
+import type { Meta, StoryObj } from "@storybook/react";
+import { CaniuseLinkBase } from "./CaniuseLinkBase";
+import "../GithubPermalink/github-permalink.css";
+
+const meta: Meta = {
+ component: CaniuseLinkBase,
+};
+
+export default meta;
+
+type Story = StoryObj;
+
+const mockFlexboxData = {
+ title: "CSS Flexible Box Layout Module",
+ description: "Method of positioning elements in horizontal or vertical stacks. Support includes all properties prefixed with `flex`, as well as `display: flex`, `display: inline-flex`, `align-content`, `align-items`, `align-self`, `justify-content` and `order`.",
+ stats: {
+ chrome: {
+ "89": "y",
+ "90": "y",
+ "91": "y",
+ "92": "y",
+ "93": "y"
+ },
+ firefox: {
+ "84": "y",
+ "85": "y",
+ "86": "y",
+ "87": "y",
+ "88": "y"
+ },
+ safari: {
+ "14.0": "y",
+ "14.1": "y",
+ "15.0": "y",
+ "15.1": "y",
+ "15.2": "y"
+ },
+ edge: {
+ "89": "y",
+ "90": "y",
+ "91": "y",
+ "92": "y",
+ "93": "y"
+ },
+ ie: {
+ "8": "n",
+ "9": "n",
+ "10": "a x",
+ "11": "a"
+ }
+ },
+ status: "ok" as const
+};
+
+export const BlockSuccess: Story = {
+ render: () => (
+
+ ),
+};
+
+export const BlockError: Story = {
+ render: () => (
+
+ ),
+};
+
+export const InlineSuccess: Story = {
+ render: () => (
+
+ This example uses {" "}
+
+ {" "} for layout.
+
+ ),
+};
+
+export const InlineError: Story = {
+ render: () => (
+
+ This example uses {" "}
+
+ {" "} for layout.
+
+ ),
+};
\ No newline at end of file
diff --git a/src/library/CaniuseLink/CaniuseLinkBase.tsx b/src/library/CaniuseLink/CaniuseLinkBase.tsx
new file mode 100644
index 0000000..cd9383a
--- /dev/null
+++ b/src/library/CaniuseLink/CaniuseLinkBase.tsx
@@ -0,0 +1,130 @@
+import { PropsWithChildren } from "react";
+import { CaniuseLinkDataResponse } from "../config/GithubPermalinkContext";
+import { ErrorMessages } from "../ErrorMessages/ErrorMessages";
+
+export type CaniuseLinkBaseProps = {
+ className?: string;
+ feature: string;
+ data: CaniuseLinkDataResponse;
+ variant?: "inline" | "block";
+}
+
+export function CaniuseLinkBase(props: CaniuseLinkBaseProps) {
+ const { data, variant = "block", feature } = props;
+
+ if (variant === "inline") {
+ if (data.status === "ok") {
+ return (
+
+
+ {data.title}
+
+
+ );
+ } else {
+ return (
+
+
+ {feature}
+
+
+ );
+ }
+ }
+
+ if (data.status === "ok") {
+ return (
+
+
+ {data.description && (
+ {data.description}
+ )}
+
+ }>
+
+
+ {Object.entries(data.stats).map(([browser, versions]) => (
+
+
+ {getBrowserName(browser)}
+
+
+ {Object.entries(versions).slice(-5).map(([version, support]) => (
+
+ {version}
+
+ ))}
+
+
+ ))}
+
+
+
+ );
+ }
+
+ return (
+
+
+
+ );
+}
+
+function CaniuseLinkInner(props: PropsWithChildren<{
+ header?: React.ReactNode
+} & {
+ feature: string;
+ className?: string;
+}>) {
+ const { feature, className = '' } = props;
+ const caniuseUrl = `https://caniuse.com/${feature}`;
+
+ return (
+
+ );
+}
+
+function getBrowserName(browser: string): string {
+ const browserNames: Record = {
+ chrome: "Chrome",
+ firefox: "Firefox",
+ safari: "Safari",
+ edge: "Edge",
+ ie: "IE",
+ opera: "Opera",
+ ios_saf: "iOS Safari",
+ and_chr: "Android Chrome",
+ and_ff: "Android Firefox",
+ samsung: "Samsung Internet"
+ };
+ return browserNames[browser] || browser;
+}
+
+function getSupportClass(support: string): string {
+ if (support === "y") return "supported";
+ if (support === "n") return "not-supported";
+ if (support.startsWith("a")) return "partial";
+ return "unknown";
+}
+
+function getSupportTitle(support: string): string {
+ if (support === "y") return "Supported";
+ if (support === "n") return "Not supported";
+ if (support.startsWith("a")) return "Partial support";
+ return "Unknown support";
+}
\ No newline at end of file
diff --git a/src/library/CaniuseLink/CaniuseLinkRsc.tsx b/src/library/CaniuseLink/CaniuseLinkRsc.tsx
new file mode 100644
index 0000000..1a85b4f
--- /dev/null
+++ b/src/library/CaniuseLink/CaniuseLinkRsc.tsx
@@ -0,0 +1,13 @@
+import { githubPermalinkRscConfig } from "../config/GithubPermalinkRscConfig";
+import { CaniuseLinkBase, CaniuseLinkBaseProps } from "./CaniuseLinkBase";
+
+export type CaniuseLinkRscProps = Omit;
+
+export async function CaniuseLinkRsc(props: CaniuseLinkRscProps) {
+ const dataFn = githubPermalinkRscConfig.getCaniuseFn();
+ const token = githubPermalinkRscConfig.getGithubToken();
+ const onError = githubPermalinkRscConfig.getOnError();
+
+ const data = await dataFn(props.feature, token, onError);
+ return ;
+}
\ No newline at end of file
diff --git a/src/library/GithubPermalink/github-permalink.css b/src/library/GithubPermalink/github-permalink.css
index 5229982..9b25107 100644
--- a/src/library/GithubPermalink/github-permalink.css
+++ b/src/library/GithubPermalink/github-permalink.css
@@ -19,6 +19,11 @@
--rgp-color-status-error: red;
+ --rgp-color-caniuse-supported: #13a10e;
+ --rgp-color-caniuse-partial: #ffb700;
+ --rgp-color-caniuse-not-supported: #d93025;
+ --rgp-color-caniuse-unknown: #666;
+
--rgp-color-reaction-foreground: #0969da;
--rgp-color-reaction-background: #ddf4ff;
@@ -62,6 +67,11 @@
--rgp-color-status-closed: rgb(130, 80, 223);
--rgp-color-status-error: red;
+ --rgp-color-caniuse-supported: #13a10e;
+ --rgp-color-caniuse-partial: #ffb700;
+ --rgp-color-caniuse-not-supported: #d93025;
+ --rgp-color-caniuse-unknown: #666;
+
--rgp-color-reaction-foreground: #4493f8;
--rgp-color-reaction-background: #388bfd33;
@@ -400,3 +410,125 @@ svg.github-logo{
margin: 0 2px;
}
}
+
+/* CaniuseLink styles */
+.react-caniuse-link {
+ border: solid 1px var(--rgp-color-border);
+ margin: 0.5em;
+ font-size: 12px;
+ border-radius: 4px;
+ text-align: left;
+ background-color: var(--rgp-color-bg-stark);
+}
+
+.react-caniuse-header {
+ background-color: var(--rgp-color-bg-frame);
+ padding: 8px;
+ border-bottom: solid 1px var(--rgp-color-border);
+}
+
+.react-caniuse-title {
+ display: flex;
+ flex-flow: row nowrap;
+ justify-content: space-between;
+ align-items: center;
+ gap: 1em;
+}
+
+.react-caniuse-title h3 {
+ margin: 0;
+ font-size: 16px;
+ color: var(--rgp-color-text-stark);
+}
+
+.react-caniuse-link a {
+ color: var(--rgp-color-file-link);
+ text-decoration: none;
+ font-size: 12px;
+}
+
+.react-caniuse-link a:hover {
+ text-decoration: underline;
+}
+
+.react-caniuse-description {
+ margin: 0.5em 0 0 0;
+ color: var(--rgp-color-text-frame);
+ font-size: 12px;
+ line-height: 1.4;
+}
+
+.react-caniuse-table {
+ padding: 12px;
+}
+
+.react-caniuse-browsers {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+}
+
+.react-caniuse-browser {
+ display: flex;
+ flex-flow: row nowrap;
+ align-items: center;
+ gap: 8px;
+}
+
+.react-caniuse-browser-name {
+ min-width: 120px;
+ font-weight: bold;
+ color: var(--rgp-color-text-stark);
+ font-size: 12px;
+}
+
+.react-caniuse-versions {
+ display: flex;
+ flex-flow: row nowrap;
+ gap: 4px;
+}
+
+.react-caniuse-version {
+ padding: 4px 8px;
+ border-radius: 4px;
+ font-size: 11px;
+ font-weight: bold;
+ color: white;
+ min-width: 40px;
+ text-align: center;
+}
+
+.react-caniuse-version.supported {
+ background-color: var(--rgp-color-caniuse-supported);
+}
+
+.react-caniuse-version.partial {
+ background-color: var(--rgp-color-caniuse-partial);
+}
+
+.react-caniuse-version.not-supported {
+ background-color: var(--rgp-color-caniuse-not-supported);
+}
+
+.react-caniuse-version.unknown {
+ background-color: var(--rgp-color-caniuse-unknown);
+}
+
+.react-caniuse-inline {
+ display: inline-block;
+ margin: 0 0.25em;
+}
+
+.react-caniuse-inline a {
+ color: var(--rgp-color-file-link);
+ text-decoration: none;
+ font-family: monospace;
+ background-color: var(--rgp-color-bg-frame);
+ padding: 0.125em 0.25em;
+ border-radius: 2px;
+ border: solid 1px var(--rgp-color-border);
+}
+
+.react-caniuse-inline a:hover {
+ text-decoration: underline;
+}
diff --git a/src/library/config/BaseConfiguration.ts b/src/library/config/BaseConfiguration.ts
index f75651f..f8d9d8d 100644
--- a/src/library/config/BaseConfiguration.ts
+++ b/src/library/config/BaseConfiguration.ts
@@ -1,5 +1,6 @@
import { defaultGetPermalinkFn } from "./defaultFunctions";
import { defaultGetIssueFn } from "./defaultFunctions";
+import { defaultGetCaniuseFn } from "./defaultFunctions";
export type BaseConfiguration = {
@@ -11,6 +12,9 @@ export type BaseConfiguration = {
/** Function to provide issue data payload */
getIssueFn: typeof defaultGetIssueFn;
+ /** Function to provide caniuse data payload */
+ getCaniuseFn: typeof defaultGetCaniuseFn;
+
/**
* A github personal access token - will be passed to the data fetching functions
*/
diff --git a/src/library/config/GithubPermalinkContext.tsx b/src/library/config/GithubPermalinkContext.tsx
index c9b7f57..6822a32 100644
--- a/src/library/config/GithubPermalinkContext.tsx
+++ b/src/library/config/GithubPermalinkContext.tsx
@@ -3,6 +3,7 @@ import { PropsWithChildren, createContext } from "react";
import { BaseConfiguration } from "./BaseConfiguration";
import { defaultGetIssueFn } from "./defaultFunctions";
import { defaultGetPermalinkFn } from "./defaultFunctions";
+import { defaultGetCaniuseFn } from "./defaultFunctions";
// Thanks ChatGPT
export type GithubPermalinkUrlInfo = {
@@ -56,6 +57,17 @@ export type GithubIssueLinkDataResponse = {
}
} | ErrorResponses;
+export type CaniuseLinkDataResponse = {
+ title: string;
+ description?: string;
+ stats: {
+ [browser: string]: {
+ [version: string]: string;
+ };
+ };
+ status: "ok";
+} | ErrorResponses;
+
@@ -63,12 +75,14 @@ export type GithubIssueLinkDataResponse = {
export const GithubPermalinkContext = createContext({
getDataFn: defaultGetPermalinkFn,
getIssueFn: defaultGetIssueFn,
+ getCaniuseFn: defaultGetCaniuseFn,
});
export function GithubPermalinkProvider(props: PropsWithChildren>) {
return
diff --git a/src/library/config/GithubPermalinkRscConfig.ts b/src/library/config/GithubPermalinkRscConfig.ts
index 0e66ded..e5c0f73 100644
--- a/src/library/config/GithubPermalinkRscConfig.ts
+++ b/src/library/config/GithubPermalinkRscConfig.ts
@@ -1,9 +1,10 @@
import { BaseConfiguration } from "./BaseConfiguration";
-import { defaultGetIssueFn, defaultGetPermalinkFn } from "./defaultFunctions";
+import { defaultGetIssueFn, defaultGetPermalinkFn, defaultGetCaniuseFn } from "./defaultFunctions";
const defaultConfiguration = {
getDataFn: defaultGetPermalinkFn,
getIssueFn: defaultGetIssueFn,
+ getCaniuseFn: defaultGetCaniuseFn,
};
class GithubPermalinkRscConfig {
@@ -23,6 +24,10 @@ class GithubPermalinkRscConfig {
return this.baseConfiguration.getIssueFn;
}
+ public getCaniuseFn() {
+ return this.baseConfiguration.getCaniuseFn;
+ }
+
public getGithubToken() {
return this.baseConfiguration.githubToken;
}
diff --git a/src/library/config/defaultFunctions.ts b/src/library/config/defaultFunctions.ts
index 63f54ad..f8bd459 100644
--- a/src/library/config/defaultFunctions.ts
+++ b/src/library/config/defaultFunctions.ts
@@ -2,6 +2,7 @@ import { GithubIssueLinkDataResponse } from "./GithubPermalinkContext";
import { parseGithubIssueLink, parseGithubPermalinkUrl } from "../utils/urlParsers";
import { GithubPermalinkDataResponse } from "./GithubPermalinkContext";
import { ErrorResponses } from "./GithubPermalinkContext";
+import { CaniuseLinkDataResponse } from "./GithubPermalinkContext";
export async function defaultGetIssueFn(issueLink: string, githubToken?: string, onError?: (err: unknown) => void): Promise {
@@ -97,3 +98,28 @@ export function handleResponse(response: Response): ErrorResponses {
};
}
+export async function defaultGetCaniuseFn(feature: string, _githubToken?: string, onError?: (err: unknown) => void): Promise {
+ try {
+ const response = await fetch(`https://raw.githubusercontent.com/Fyrd/caniuse/main/features-json/${feature}.json`);
+
+ if (!response.ok) {
+ onError?.(response);
+ return handleResponse(response);
+ }
+
+ const data = await response.json();
+
+ return {
+ title: data.title,
+ description: data.description,
+ stats: data.stats,
+ status: "ok"
+ };
+ } catch (error) {
+ onError?.(error);
+ return {
+ status: "other-error"
+ };
+ }
+}
+
diff --git a/src/library/export.ts b/src/library/export.ts
index 31db22b..b4ee39c 100644
--- a/src/library/export.ts
+++ b/src/library/export.ts
@@ -3,4 +3,6 @@ export * from "./GithubPermalink/GithubPermalink";
export * from "./GithubPermalink/GithubPermalinkBase";
export * from "./GithubIssueLink/GithubIssueLink";
export * from "./GithubIssueLink/GithubIssueLinkBase"
+export * from "./CaniuseLink/CaniuseLink";
+export * from "./CaniuseLink/CaniuseLinkBase";
export * from "./config/GithubPermalinkContext";
diff --git a/src/library/rsc.ts b/src/library/rsc.ts
index ce35b3d..c6cc6d7 100644
--- a/src/library/rsc.ts
+++ b/src/library/rsc.ts
@@ -1,3 +1,4 @@
export * from "./GithubPermalink/GithubPermalinkRsc";
export * from "./config/GithubPermalinkRscConfig";
-export * from "./GithubIssueLink/GithubIssueLinkRsc";
\ No newline at end of file
+export * from "./GithubIssueLink/GithubIssueLinkRsc";
+export * from "./CaniuseLink/CaniuseLinkRsc";
\ No newline at end of file