Skip to content

Commit 268f45d

Browse files
Add changes to switcher, tabs, and codeblock from universal gateway restructure (#1337)
I decided to take the tabs and code blocks updates in #1224 and merge them separately into main so we can have them. This PR: - Replaces the docusaurus `Tabs` component with mantle tabs, which means the entire docs site no longer uses the old docusaurus style tabs anymore. ([Preview here](https://ngrok-docs-git-shaquil-doc-266-copy-over-tabs-554909-ngrok-dev.vercel.app/docs/traffic-policy/actions/add-headers/#example-traffic-policy-document)) https://github.com/user-attachments/assets/1fb66470-24e2-4df7-92d1-bc1cd3a29ee1 - Adds the language tab to singular code block components ([Preview here](https://ngrok-docs-git-shaquil-doc-266-copy-over-tabs-554909-ngrok-dev.vercel.app/docs/agent/ssh-reverse-tunnel-agent/#custom-domain)) <img width="731" alt="image" src="https://github.com/user-attachments/assets/20c95908-a192-4a2b-8b45-374d7b51a252" /> - Adds the book icon with links to the SDK docs ([Preview here](https://ngrok-docs-git-shaquil-doc-266-copy-over-tabs-554909-ngrok-dev.vercel.app/docs/agent/agent-tls-termination#step-4--start-your-upstream-server)) https://github.com/user-attachments/assets/57cf3b6b-550d-4d56-a7cd-36dd0fb353dc ## This PR also Prevents the ConfigExample component from showing the Agent Config variant by default. Now it only shows the Traffic Policy version by default --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
1 parent a13254f commit 268f45d

File tree

22 files changed

+921
-162
lines changed

22 files changed

+921
-162
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
These guides exist to walk you through using some of the interactive components in the docs, such as `<LangSwitcher>`, `<Tabs>`, and more.
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
Table of Contents
2+
3+
- [Creating code blocks](#creating-code-blocks)
4+
- [Valid properties](#valid-properties)
5+
- [`tabName`](#-tabname-)
6+
- [`title`](#-title-)
7+
- [`titleLink`](#-titlelink-)
8+
- [`mode`](#-mode-)
9+
- [`disableCopy`](#-disablecopy-)
10+
- [`collapsible`](#-collapsible-)
11+
- [`collapseLineNumber`](#-collapselinenumber-)
12+
- [`indentation`](#-indentation-)
13+
14+
This guide is for internal ngrok teammates who want to use the codeblock and LangSwitcher components to render code snippets in the docs.
15+
16+
# Creating code blocks
17+
18+
A standard codeblock will look like this:
19+
20+
````txt
21+
```bash title="file-title"
22+
openssl req -new -newkey rsa:4096 -x509 -sha256 -days 365 -noenc -out your-cert.crt -keyout your-key.key
23+
```
24+
````
25+
26+
Result:
27+
![An example codeblock](./img/default.png)
28+
29+
## Valid properties
30+
31+
The code block can take the following meta properties, which you use by adding them to the same line where you specify the language.
32+
33+
### `tabName`
34+
35+
Use `tabName` to rename the language tab shown in the code block component.
36+
37+
Example:
38+
39+
````txt
40+
```bash tabName="Example"
41+
openssl req -new -newkey rsa:4096 -x509 -sha256 -days 365 -noenc -out your-cert.crt -keyout your-key.key
42+
```
43+
````
44+
45+
Result:
46+
![Screenshot of a language tab after tabName has been applied](./img/tabNameResult.png)
47+
48+
### `title`
49+
50+
Use `title` to add a file name to a code block component.
51+
52+
Example:
53+
54+
````txt
55+
```bash title="Example file"
56+
openssl req -new -newkey rsa:4096 -x509 -sha256 -days 365 -noenc -out your-cert.crt -keyout your-key.key
57+
```
58+
````
59+
60+
Result:
61+
![An example codeblock with a title prop](./img/titleResult.png)
62+
63+
#### `titleLink`
64+
65+
After defining a `title`, use `titleLink` wrap a link around the file name of a codeblock.
66+
67+
Example:
68+
69+
````txt
70+
```bash title="Example file" titleLink=https://ngrok.com/pricing
71+
openssl req -new -newkey rsa:4096 -x509 -sha256 -days 365 -noenc -out your-cert.crt -keyout your-key.key
72+
```
73+
````
74+
75+
![An example codeblock with title and titleLink props](./img/titleLinkResult.png)
76+
77+
### `mode`
78+
79+
Define which file icon appears next to the `title` of a codeblock.
80+
81+
Example:
82+
83+
````txt
84+
```bash title="Example file" mode=traffic-policy
85+
openssl req -new -newkey rsa:4096 -x509 -sha256 -days 365 -noenc -out your-cert.crt -keyout your-key.key
86+
```
87+
````
88+
89+
![An example codeblock with title and titleLink props](./img/modeResult.png)
90+
91+
### `disableCopy`
92+
93+
Boolean. Decide if the "copy code" button will appear in the codeblock
94+
95+
Example:
96+
97+
````txt
98+
```bash disableCopy=true
99+
openssl req -new -newkey rsa:4096 -x509 -sha256 -days 365 -noenc -out your-cert.crt -keyout your-key.key
100+
```
101+
````
102+
103+
![An example codeblock using the disableCopy prop](./img/disableCopyResult.png)
104+
105+
### `collapsible`
106+
107+
Default `false`. Decide if a codeblock will start off collapsed, truncating what's shown. The user can then expand the codeblock to see the rest.
108+
109+
Example:
110+
111+
````txt
112+
```bash collapsible
113+
openssl req -new -newkey rsa:4096 -x509 -sha256 -days 365 -noenc -out your-cert.crt -keyout your-key.key
114+
```
115+
````
116+
117+
#### `collapseLineNumber`
118+
119+
Choose which line number codeblock will be collapsed at.
120+
121+
Example:
122+
123+
````txt
124+
```bash title="Example file" collapsible collapseLine=20
125+
openssl req -new -newkey rsa:4096 -x509 -sha256 -days 365 -noenc -out your-cert.crt -keyout your-key.key
126+
```
127+
````
128+
129+
## `indentation`
130+
131+
Default `undefined`. Choose whether the code block should be indented with `tabs` or `spaces`.
132+
133+
In general, don't use this property unless you have a specific reason. The codeblock automatically chooses the appropriate indentation based on the language.
134+
135+
Example:
136+
137+
````txt
138+
```js indendation=spaces
139+
function example(){
140+
return console.log("example");
141+
}
142+
```
143+
````
Loading
Loading
Loading
Loading
Loading
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import useBaseUrl from "@docusaurus/useBaseUrl";
2+
import {
3+
CodeBlock,
4+
CodeBlockBody,
5+
CodeBlockCode,
6+
CodeBlockCopyButton,
7+
CodeBlockExpanderButton,
8+
CodeBlockHeader,
9+
CodeBlockIcon,
10+
CodeBlockTitle,
11+
fmtCode,
12+
} from "@ngrok/mantle/code-block";
13+
import clsx from "clsx";
14+
import type { ReactNode } from "react";
15+
import { SdkButton } from "../LangSwitcher/SdkButton";
16+
import type { LanguageInfo } from "../LangSwitcher/data";
17+
18+
type CodeBlockWithInfoProps = {
19+
content: string | undefined;
20+
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
21+
language: any;
22+
collapseLineNumber: number;
23+
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
24+
meta: any;
25+
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
26+
className?: any;
27+
headerContent: ReactNode;
28+
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
29+
codeBlockProps?: any;
30+
info?: LanguageInfo | undefined;
31+
};
32+
33+
export function CodeBlockWithInfo({
34+
content,
35+
language,
36+
collapseLineNumber,
37+
meta,
38+
className,
39+
headerContent,
40+
info,
41+
codeBlockProps,
42+
}: CodeBlockWithInfoProps) {
43+
const collapsible = !meta
44+
? false
45+
: meta.collapsible &&
46+
content &&
47+
content.split("\n").length > collapseLineNumber;
48+
49+
return (
50+
<div className="flex flex-col">
51+
<CodeBlock className={className} {...codeBlockProps}>
52+
<CodeBlockHeader className={clsx("flex w-[100%] justify-start p-2")}>
53+
{headerContent}
54+
{info && <SdkButton className="ml-auto mr-0.5" data={info} />}
55+
</CodeBlockHeader>
56+
<CodeBlockBody>
57+
{meta?.title && (
58+
<div className="mx-2 mt-3.5 flex w-[100%] items-end justify-start gap-1">
59+
<>
60+
{meta?.mode ? (
61+
<CodeBlockIcon preset={meta.mode} />
62+
) : (
63+
<CodeBlockIcon preset="file" />
64+
)}
65+
<CodeBlockTitle>
66+
{meta?.titleLink ? (
67+
<a href={useBaseUrl(meta.titleLink)}>
68+
<strong>{meta.title}</strong>
69+
</a>
70+
) : (
71+
<strong>{meta?.title}</strong>
72+
)}
73+
</CodeBlockTitle>
74+
</>
75+
</div>
76+
)}
77+
{!meta?.disableCopy && <CodeBlockCopyButton />}
78+
<CodeBlockCode
79+
indentation={meta?.indentation}
80+
language={language}
81+
value={fmtCode`${content}`}
82+
/>
83+
{collapsible && <CodeBlockExpanderButton />}
84+
</CodeBlockBody>
85+
</CodeBlock>
86+
</div>
87+
);
88+
}

src/components/ConfigExample.tsx

Lines changed: 32 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@ngrok/mantle/tabs";
1+
import TabItem from "@theme/TabItem";
2+
import Tabs from "@theme/Tabs";
23
import type { ReactNode } from "react";
34
import YAML, { type ToStringOptions } from "yaml";
45
import { LangSwitcher } from "./LangSwitcher";
@@ -69,14 +70,14 @@ export type ConfigExampleProps = {
6970
jsonMetastring?: string;
7071
title?: string;
7172
icon?: ReactNode;
72-
hideAgentConfig?: boolean;
73-
hideTrafficPolicy?: boolean;
73+
showAgentConfig?: boolean;
74+
showTrafficPolicy?: boolean;
7475
};
7576

7677
export default function ConfigExample({
7778
// Show the agent config by default
78-
hideAgentConfig = false,
79-
hideTrafficPolicy = false,
79+
showAgentConfig = false,
80+
showTrafficPolicy = true,
8081
...props
8182
}: ConfigExampleProps) {
8283
const yamlOptions = {
@@ -103,29 +104,35 @@ export default function ConfigExample({
103104
agentConfig.yamlConfig,
104105
agentConfig.jsonConfig,
105106
);
106-
if (hideAgentConfig && hideTrafficPolicy)
107+
108+
// if both false, throw error;
109+
if (!showTrafficPolicy && !showAgentConfig) {
107110
throw new Error(
108-
"At least one of hideAgentConfig or hideTrafficPolicy must be false",
111+
"ConfigExample error: One of showTrafficPolicy or showAgentConfig must be true",
109112
);
113+
}
114+
115+
// if only one is true, no need for <Tabs></Tabs>
116+
if (!showAgentConfig) {
117+
return policySnippet;
118+
}
119+
if (!showTrafficPolicy) {
120+
return agentConfigSnippet;
121+
}
122+
110123
return (
111-
<Tabs
112-
orientation="horizontal"
113-
defaultValue={hideTrafficPolicy ? "agent-config" : "traffic-policy"}
114-
>
115-
<TabsList>
116-
{hideTrafficPolicy ? null : (
117-
<TabsTrigger value="traffic-policy">Traffic Policy</TabsTrigger>
118-
)}
119-
{hideAgentConfig ? null : (
120-
<TabsTrigger value="agent-config">Agent Config</TabsTrigger>
121-
)}
122-
</TabsList>
123-
{hideTrafficPolicy ? null : (
124-
<TabsContent value="traffic-policy">{policySnippet}</TabsContent>
125-
)}
126-
{hideAgentConfig ? null : (
127-
<TabsContent value="agent-config">{agentConfigSnippet}</TabsContent>
128-
)}
124+
<Tabs groupId="config-example" queryString="config-example">
125+
{showTrafficPolicy ? (
126+
<TabItem value="traffic-policy" label="Traffic Policy" default>
127+
{policySnippet}
128+
</TabItem>
129+
) : null}
130+
131+
{showAgentConfig ? (
132+
<TabItem value="agent-config" label="Agent Config">
133+
{agentConfigSnippet}
134+
</TabItem>
135+
) : null}
129136
</Tabs>
130137
);
131138
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { useContext } from "react";
2+
import type { LangSwitcherContextType } from "./LangSwitcherContext";
3+
import LangSwitcherContext from "./LangSwitcherContext";
4+
5+
/**
6+
* Renders its children only if the specified language matches the
7+
* currently selected language for the LangSwithcher component.
8+
* This is useful for conditionally rendering content based on the
9+
* selected language.
10+
*/
11+
export function ContentSwitcher({
12+
children,
13+
languages,
14+
}: {
15+
children: React.ReactNode;
16+
languages: string[];
17+
className?: string;
18+
}) {
19+
const { selectedLanguage } =
20+
useContext<LangSwitcherContextType>(LangSwitcherContext);
21+
if (!languages?.length)
22+
throw new Error("Must specify a language for the ContentSwitcher");
23+
24+
for (const lang of languages) {
25+
if (lang === selectedLanguage) {
26+
return <div className="mt-[1rem]">{children}</div>;
27+
}
28+
}
29+
return null;
30+
}

src/components/LangSwitcher/LangSwitcherContext.tsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,16 @@ import type { SupportedLanguage } from "@ngrok/mantle/code-block";
22
import { createContext } from "react";
33

44
export type LangSwitcherContextType = {
5-
tabLanguage: string | SupportedLanguage | null;
5+
selectedLanguage: string | SupportedLanguage | null;
66
defaultLanguage: string | null;
7-
updateTab: null | ((newLang: string | SupportedLanguage) => void);
7+
updateSelectedLanguage:
8+
| null
9+
| ((newLang: string | SupportedLanguage | undefined) => void);
810
};
911

1012
const LangSwitcherContext = createContext<LangSwitcherContextType>({
11-
tabLanguage: "",
12-
updateTab: null,
13+
selectedLanguage: null,
14+
updateSelectedLanguage: null,
1315
defaultLanguage: null,
1416
});
1517

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { Button } from "@ngrok/mantle/button";
2+
3+
export function LangTab({
4+
tabText,
5+
disabled = false,
6+
className,
7+
onClick,
8+
}: {
9+
tabText: string;
10+
className?: string;
11+
disabled?: boolean;
12+
onClick?: () => void;
13+
}) {
14+
return (
15+
<Button
16+
disabled={disabled}
17+
onClick={onClick}
18+
type="button"
19+
priority="neutral"
20+
appearance="ghost"
21+
className={className}
22+
>
23+
{tabText}
24+
</Button>
25+
);
26+
}

0 commit comments

Comments
 (0)