Skip to content

Commit

Permalink
feat: SDK install component (#1109)
Browse files Browse the repository at this point in the history
Co-authored-by: Andrew Jiang <[email protected]>
  • Loading branch information
trevorblades and abvthecity authored Jul 10, 2024
1 parent bebed04 commit f7df8e4
Show file tree
Hide file tree
Showing 20 changed files with 335 additions and 19 deletions.
2 changes: 1 addition & 1 deletion packages/ui/app/src/api-page/endpoints/EndpointUrl.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { APIV1Read } from "@fern-api/fdr-sdk";
import { CopyToClipboardButton } from "@fern-ui/components";
import { visitDiscriminatedUnion } from "@fern-ui/core-utils";
import cn from "clsx";
import React, { PropsWithChildren, ReactElement, useImperativeHandle, useMemo, useRef } from "react";
import { parse } from "url";
import { buildRequestUrl } from "../../api-playground/utils";
import { HttpMethodTag } from "../../commons/HttpMethodTag";
import { ResolvedEndpointPathParts } from "../../resolver/types";
import { CopyToClipboardButton } from "../../syntax-highlighting/CopyToClipboardButton";
import { divideEndpointPathToParts, type EndpointPathPart } from "../../util/endpoint";

export declare namespace EndpointUrl {
Expand Down
3 changes: 1 addition & 2 deletions packages/ui/app/src/api-page/examples/TitledExample.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { Intent } from "@fern-ui/components";
import { CopyToClipboardButton, Intent } from "@fern-ui/components";
import cn from "clsx";
import { forwardRef, MouseEventHandler, PropsWithChildren, ReactElement, ReactNode } from "react";
import { CopyToClipboardButton } from "../../syntax-highlighting/CopyToClipboardButton";

export declare namespace TitledExample {
export interface Props {
Expand Down
3 changes: 1 addition & 2 deletions packages/ui/app/src/api-page/web-socket/WebSocket.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { APIV1Read, FernNavigation } from "@fern-api/fdr-sdk";
import { FernScrollArea } from "@fern-ui/components";
import { CopyToClipboardButton, FernScrollArea } from "@fern-ui/components";
import { ArrowDownIcon, ArrowUpIcon } from "@radix-ui/react-icons";
import cn from "clsx";
import { Children, FC, HTMLAttributes, ReactNode, useMemo } from "react";
Expand All @@ -19,7 +19,6 @@ import {
stringifyResolvedEndpointPathParts,
unwrapReference,
} from "../../resolver/types";
import { CopyToClipboardButton } from "../../syntax-highlighting/CopyToClipboardButton";
import { getSlugFromChildren } from "../../util/getSlugFromText";
import { ApiPageDescription } from "../ApiPageDescription";
import { EndpointParameter } from "../endpoints/EndpointParameter";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { APIV1Read } from "@fern-api/fdr-sdk";
import { CopyToClipboardButton } from "@fern-ui/components";
import * as Accordion from "@radix-ui/react-accordion";
import { ArrowDownIcon, ArrowUpIcon, ChevronDownIcon } from "@radix-ui/react-icons";
import cn from "clsx";
import { FC } from "react";
import { CopyToClipboardButton } from "../../syntax-highlighting/CopyToClipboardButton";
import { FernSyntaxHighlighter } from "../../syntax-highlighting/FernSyntaxHighlighter";

export interface WebSocketMessage {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
CopyToClipboardButton,
FernAudioPlayer,
FernButton,
FernButtonGroup,
Expand All @@ -19,7 +20,6 @@ import { useDomain } from "../atoms/navigation";
import { IS_MOBILE_SCREEN_ATOM } from "../atoms/viewport";
import { FernErrorTag } from "../components/FernErrorBoundary";
import { ResolvedEndpointDefinition, ResolvedTypeDefinition } from "../resolver/types";
import { CopyToClipboardButton } from "../syntax-highlighting/CopyToClipboardButton";
import { PlaygroundAuthorizationFormCard } from "./PlaygroundAuthorizationForm";
import { PlaygroundEndpointForm } from "./PlaygroundEndpointForm";
import { PlaygroundEndpointFormButtons } from "./PlaygroundEndpointFormButtons";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { APIV1Read } from "@fern-api/fdr-sdk";
import { FernButton } from "@fern-ui/components";
import { CopyToClipboardButton, FernButton } from "@fern-ui/components";
import { visitDiscriminatedUnion } from "@fern-ui/core-utils";
import * as Dialog from "@radix-ui/react-dialog";
import { Cross1Icon } from "@radix-ui/react-icons";
Expand All @@ -8,7 +8,6 @@ import { isUndefined, omitBy } from "lodash-es";
import { FC, Fragment, ReactNode } from "react";
import { HttpMethodTag } from "../commons/HttpMethodTag";
import { ResolvedEndpointPathParts, ResolvedObjectProperty } from "../resolver/types";
import { CopyToClipboardButton } from "../syntax-highlighting/CopyToClipboardButton";
import { PlaygroundSendRequestButton } from "./PlaygroundSendRequestButton";
import { PlaygroundRequestFormState } from "./types";
import { buildRequestUrl, unknownToString } from "./utils";
Expand Down
2 changes: 1 addition & 1 deletion packages/ui/app/src/mdx/components/CodeGroup.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { CopyToClipboardButton } from "@fern-ui/components";
import * as Tabs from "@radix-ui/react-tabs";
import clsx from "clsx";
import { useState } from "react";
import { useFeatureFlags } from "../../atoms/flags";
import { HorizontalOverflowMask } from "../../commons/HorizontalOverflowMask";
import { CopyToClipboardButton } from "../../syntax-highlighting/CopyToClipboardButton";
import { FernSyntaxHighlighter, FernSyntaxHighlighterProps } from "../../syntax-highlighting/FernSyntaxHighlighter";

export declare namespace CodeGroup {
Expand Down
8 changes: 8 additions & 0 deletions packages/ui/app/src/mdx/components/InstallSdk.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { FernSdk } from "@fern-ui/components";
import { useAtom } from "jotai";
import { FERN_LANGUAGE_ATOM } from "../../atoms/lang";

export const InstallSdk: React.FC<Pick<React.ComponentProps<typeof FernSdk>, "sdks">> = ({ sdks }) => {
const [selectedLanguage, setSelectedLanguage] = useAtom(FERN_LANGUAGE_ATOM);
return <FernSdk sdks={sdks} language={selectedLanguage} onChange={setSelectedLanguage} />;
};
2 changes: 2 additions & 0 deletions packages/ui/app/src/mdx/mdx-components.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import { Column, ColumnGroup } from "./components/ColumnGroup";
import { Frame } from "./components/Frame";
import { HTML_TABLE_COMPONENTS } from "./components/HTMLTable";
import { IFrame } from "./components/IFrame";
import { InstallSdk } from "./components/InstallSdk";
import { ParamField } from "./components/ParamField";
import { EndpointRequestSnippet, EndpointResponseSnippet } from "./components/RequestSnippet";
import { Steps } from "./components/Steps";
Expand Down Expand Up @@ -66,6 +67,7 @@ export const JSX_COMPONENTS = {
Steps,
TabGroup,
Tooltip,
InstallSdk,

// deprecated, aliased for backwards compatibility
Cards: CardGroup,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { CopyToClipboardButton } from "@fern-ui/components";
import cn, { clsx } from "clsx";
import React, { PropsWithChildren } from "react";
import { useFeatureFlags } from "../atoms/flags";
import { CopyToClipboardButton } from "./CopyToClipboardButton";

type CodeBlockWithClipboardButtonProps = {
code: string;
Expand Down
8 changes: 7 additions & 1 deletion packages/ui/components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-feather": "^2.0.10",
"sonner": "^1.4.41"
"sonner": "^1.4.41",
"ts-extras": "^0.11.0"
},
"devDependencies": {
"@chromatic-com/storybook": "^1.4.0",
Expand All @@ -67,14 +68,18 @@
"@storybook/addon-onboarding": "^8.1.1",
"@storybook/addon-themes": "^8.1.1",
"@storybook/blocks": "^8.1.1",
"@storybook/preview-api": "^8.1.11",
"@storybook/react": "^8.1.1",
"@storybook/react-vite": "^8.1.1",
"@storybook/test": "^8.1.1",
"@tailwindcss/forms": "^0.5.7",
"@tailwindcss/typography": "^0.5.10",
"@testing-library/jest-dom": "^6.4.2",
"@testing-library/react": "^14.2.1",
"@types/node": "^18.7.18",
"@types/react": "^18.0.20",
"@types/react-dom": "^18.2.18",
"@types/react-test-renderer": "^18.0.7",
"@vitejs/plugin-react": "^4.2.1",
"autoprefixer": "^10.4.16",
"chromatic": "^11.3.0",
Expand All @@ -85,6 +90,7 @@
"organize-imports-cli": "^0.10.0",
"postcss-import": "^16.0.1",
"prettier": "^3.3.2",
"react-test-renderer": "^18.2.0",
"sass": "^1.74.1",
"storybook": "^8.1.1",
"stylelint": "^16.1.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { FernButton, FernTooltip, FernTooltipProvider } from "@fern-ui/components";
import { useCopyToClipboard } from "@fern-ui/react-commons";
import { CopyIcon } from "@radix-ui/react-icons";
import cn from "clsx";
import { Check } from "react-feather";
import { FernButton } from "./FernButton";
import { FernTooltip, FernTooltipProvider } from "./FernTooltip";

export declare namespace CopyToClipboardButton {
export interface Props {
Expand Down
163 changes: 163 additions & 0 deletions packages/ui/components/src/FernSdk.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
import cn from "clsx";
import { arrayIncludes, objectKeys } from "ts-extras";
import { CopyToClipboardButton } from "./CopyToClipboardButton";
import { RemoteFontAwesomeIcon } from "./FontAwesomeIcon";

const languages = ["node", "python", "java", "ruby", "go", "csharp", "swift"] as const;
type SdkLanguage = (typeof languages)[number];

type InstallCommand = (packageName: string) => string;

const languageProps: Record<
SdkLanguage,
{
name: string;
icon: string;
installCommand: InstallCommand;
color?: string;
}
> = {
java: {
name: "Java",
icon: "fa-brands fa-java",
installCommand: (packageName: string) => `implementation '${packageName}'`,
},
node: {
name: "Node.js",
icon: "fa-brands fa-node-js",
color: "#5FA04E",
installCommand: (packageName: string) => `npm install ${packageName}`,
},
python: {
name: "Python",
icon: "fa-brands fa-python",
color: "#3776AB",
installCommand: (packageName: string) => `pip install ${packageName}`,
},
ruby: {
name: "Ruby",
icon: "fa-solid fa-gem",
color: "#CC342D",
installCommand: (packageName: string) => `gem install ${packageName}`,
},
go: {
name: "Go",
icon: "fa-brands fa-golang",
color: "#00ADD8",
installCommand: (packageName: string) => `go get ${packageName}`,
},
csharp: {
name: "C#",
icon: "fa-brands fa-microsoft",
color: "#68217A",
installCommand: (packageName: string) => `dotnet add package ${packageName}`,
},
swift: {
name: "Swift",
icon: "fa-brands fa-swift",
color: "#F05138",
installCommand: () => "Edit your Package.swift file",
},
};

const FernSdkInstallCommand: React.FC<{
installCommand: InstallCommand;
packageName: string;
}> = ({ installCommand, packageName }) => {
const command = installCommand(packageName);
return (
<>
<pre>
<code>
<span className="t-muted">$</span> {command}
</code>
</pre>
<CopyToClipboardButton className="absolute top-2.5 right-2.5" content={command} />
</>
);
};

const normalizeLanguage = (language: string): SdkLanguage | undefined => {
if (arrayIncludes(languages, language)) {
return language;
}

switch (language) {
case "js":
case "ts":
case "javascript":
case "typescript":
case "nodejs":
case "ts-node":
return "node";
case "py":
return "python";
case "golang":
return "go";
default:
return undefined;
}
};

export const FernSdk: React.FC<{
sdks: Partial<
Record<
SdkLanguage,
{
packageName: string;
installCommand?: InstallCommand;
}
>
>;
language: string;
onChange: (language: string) => void;
}> = ({ sdks, language, onChange }) => {
const activeLanguage = normalizeLanguage(language);
const activeSdk = activeLanguage && sdks[activeLanguage];
return (
<div className="border border-default rounded-lg overflow-hidden">
<div className="bg-background">
<div className="px-3 py-2 text-xs font-medium uppercase">Client libraries</div>
<div className="flex justify-center">
{objectKeys(sdks).map((lang, index) => {
const { icon, name, color } = languageProps[lang];
return (
<button
className={cn(
"py-2 w-16 flex flex-col items-center gap-2 text-center border-b-2",
activeLanguage === lang ? "border-primary" : "border-transparent",
)}
key={lang}
onClick={() => {
onChange(lang);
}}
>
<RemoteFontAwesomeIcon
className="mx-auto"
key={index}
color={color}
icon={icon}
size={6}
/>
<div className="text-xs pb-0.5">{name}</div>
</button>
);
})}
</div>
</div>
<div className="bg-background-tertiary-light px-3 py-4 dark:bg-background-tertiary-dark border-t border-default text-sm relative">
{activeSdk ? (
<FernSdkInstallCommand
installCommand={activeSdk.installCommand ?? languageProps[activeLanguage].installCommand}
packageName={activeSdk.packageName}
/>
) : (
<>
By default, these docs demonstrate using curl to interact with the API over HTTP. Select one of
our official client libraries to see examples in code.
</>
)}
</div>
</div>
);
};
9 changes: 4 additions & 5 deletions packages/ui/components/src/FontAwesomeIcon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,10 @@ function getIconUrl(icon: string | undefined): string {
}

function getCdnHost() {
const CDN_HOST = process?.env?.NEXT_PUBLIC_FONTAWESOME_CDN_HOST;
if (CDN_HOST == null) {
return "https://icons.ferndocs.com";
}
return CDN_HOST;
return (
(typeof process !== "undefined" ? process.env.NEXT_PUBLIC_FONTAWESOME_CDN_HOST : undefined) ??
"https://icons.ferndocs.com"
);
}

function parseFontAwesomeIcon(icon: string): [string, string] | undefined {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Object.defineProperty(navigator, "clipboard", {

import { cleanup, fireEvent, render } from "@testing-library/react";
import renderer from "react-test-renderer";
import { CopyToClipboardButton } from "../../syntax-highlighting/CopyToClipboardButton";
import { CopyToClipboardButton } from "../CopyToClipboardButton";

afterEach(cleanup);

Expand Down
9 changes: 9 additions & 0 deletions packages/ui/components/src/__test__/setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import * as matchers from "@testing-library/jest-dom/matchers";
import { cleanup } from "@testing-library/react";
import { afterEach, expect } from "vitest";

expect.extend(matchers);

afterEach(() => {
cleanup();
});
2 changes: 2 additions & 0 deletions packages/ui/components/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from "./CopyToClipboardButton";
export * from "./Empty";
export * from "./FernAudioPlayer";
export * from "./FernButton";
Expand All @@ -11,6 +12,7 @@ export * from "./FernModal";
export * from "./FernNumericInput";
export * from "./FernRadioGroup";
export * from "./FernScrollArea";
export * from "./FernSdk";
export * from "./FernSegmentedControl";
export * from "./FernSelect";
export * from "./FernSwitch";
Expand Down
Loading

0 comments on commit f7df8e4

Please sign in to comment.