Skip to content

Commit 6b43827

Browse files
chore: clean up interface, use method+path
1 parent 2948d4d commit 6b43827

File tree

3 files changed

+46
-20
lines changed

3 files changed

+46
-20
lines changed

src/react-components/code-sample.state.tsx

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,28 +46,28 @@ type Action =
4646
| { type: "SELECT"; payload: SafeGetSnippetParams };
4747

4848
type SafeGetSnippetParams = {
49-
operationId?: string | undefined;
49+
methodPath?: string | undefined;
5050
language?: string | undefined;
5151
};
5252

5353
function safeGetSnippet(
5454
snippets: SpeakeasyHighlightedCode[],
55-
{ operationId, language }: SafeGetSnippetParams,
55+
{ methodPath, language }: SafeGetSnippetParams,
5656
): SpeakeasyHighlightedCode {
5757
const maybeEqual = (a: string, b: string | undefined) =>
5858
b === undefined || a.toLowerCase() === b.toLowerCase();
5959

6060
const selectedSnippet = snippets.find(
6161
(s) =>
62-
maybeEqual(s.raw.operationId, operationId) &&
62+
maybeEqual(getMethodPath(s.raw), methodPath) &&
6363
maybeEqual(s.lang, language),
6464
);
6565
if (selectedSnippet) {
6666
return selectedSnippet;
6767
}
6868

6969
console.warn(
70-
`Could not find snippet for operationId "${operationId}".`,
70+
`Could not find snippet for method and path "${methodPath}".`,
7171
`Falling back to to first language in snippet array.`,
7272
);
7373

@@ -159,7 +159,7 @@ export const useCodeSampleState = ({
159159
type: "SELECT",
160160
payload: {
161161
language: state.selectedSnippet?.raw.language,
162-
operationId: state.selectedSnippet?.raw.operationId,
162+
methodPath: getMethodPath(state.selectedSnippet?.raw),
163163
...params,
164164
},
165165
});
@@ -168,6 +168,9 @@ export const useCodeSampleState = ({
168168
return { state, selectSnippet };
169169
};
170170

171+
export const getMethodPath = (snippet: UsageSnippet | undefined): string =>
172+
snippet ? `${snippet.method.toUpperCase()} ${snippet.path}` : "";
173+
171174
/** Intended to give the user the option of providing their own client. */
172175
export const useSafeSpeakeasyCodeSamplesContext = (
173176
coreClient?: SpeakeasyCodeSamplesCore,

src/react-components/code-sample.tsx

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
MethodPaths,
77
} from "../models/operations/getcodesamples.js";
88
import { OperationId } from "../types/custom.js";
9-
import { useCodeSampleState } from "./code-sample.state.js";
9+
import { getMethodPath, useCodeSampleState } from "./code-sample.state.js";
1010
import classes from "./code-sample.styles.js";
1111
import { CodeViewer, ErrorDisplay } from "./code-viewer.js";
1212
import codehikeTheme from "./codehike/theme.js";
@@ -20,15 +20,15 @@ import {
2020
} from "./titles.js";
2121
import { prettyLanguageName } from "./utils.js";
2222
import { Selector } from "./selector";
23+
import { UsageSnippet } from "../models/components";
2324

2425
export type CodeSamplesViewerProps = {
2526
/** Whether the code snippet should be copyable. */
2627
copyable?: boolean;
2728

2829
/** Default language to show in the code playground. If not found in the snippets, the first one will be used. */
2930
defaultLanguage?: string;
30-
/** Default operation to show in the code playground. If not found in the snippets, the first one will be used. */
31-
defaultOperation?: string;
31+
3232
/**
3333
* The color mode for the code playground. If "system", the component will
3434
* detect the system color scheme automagically.
@@ -40,12 +40,13 @@ export type CodeSamplesViewerProps = {
4040
* A component to render as the snippet title in the upper-right corner of
4141
* the component. Receives data about the selected code sample. The library
4242
* comes pre-packaged with some sensible options.
43+
* If set to false, no title bar will be shown.
4344
*
4445
* @see CodeSampleTitle
4546
* @see CodeSampleFilenameTitle
4647
* @default CodeSampleMethodTitle
4748
*/
48-
title?: CodeSampleTitleComponent | React.ReactNode | string | "none";
49+
title?: CodeSampleTitleComponent | React.ReactNode | string | false;
4950
/** The operations to get code samples for. If only one is provided, no selector will be shown.
5051
* Can be queried by either operationId or method+path.
5152
*/
@@ -68,7 +69,6 @@ export function CodeSamplesViewer({
6869
theme = "system",
6970
title = CodeSampleFilenameTitle,
7071
defaultLanguage,
71-
defaultOperation,
7272
operations,
7373
copyable,
7474
client: clientProp,
@@ -93,7 +93,7 @@ export function CodeSamplesViewer({
9393
// On mount, select the defaults
9494
useEffect(() => {
9595
if (!state.snippets || state.status !== "success") return;
96-
selectSnippet({ language: defaultLanguage, operationId: defaultOperation });
96+
selectSnippet({ language: defaultLanguage });
9797
}, [state.status]);
9898

9999
const systemColorMode = useSystemColorMode();
@@ -110,12 +110,28 @@ export function CodeSamplesViewer({
110110
];
111111
}, [state.snippets]);
112112

113-
const operationIds: string[] = useMemo(() => {
114-
return [
115-
...new Set(state.snippets?.map(({ raw }) => raw.operationId) ?? []),
116-
];
113+
const getOperationKey = (snippet: UsageSnippet | undefined): string => {
114+
let { operationId } = snippet;
115+
const methodPathDisplay = getMethodPath(snippet);
116+
if (!operationId) {
117+
operationId = methodPathDisplay;
118+
}
119+
return operationId;
120+
};
121+
122+
// We need this methodAndPath stuff because not all snippets will have operation ids
123+
// For the selector, we try to show operation ID but fall back on method+path if it's missing
124+
const operationIdToMethodAndPath: Record<string, string> = useMemo(() => {
125+
return Object.fromEntries(
126+
state.snippets?.map(({ raw }) => [
127+
getOperationKey(raw),
128+
getMethodPath(raw),
129+
]) ?? [],
130+
);
117131
}, [state.snippets]);
118132

133+
const operationIds = Object.keys(operationIdToMethodAndPath);
134+
119135
return (
120136
<LazyMotion strict features={domMax}>
121137
<div
@@ -128,7 +144,7 @@ export function CodeSamplesViewer({
128144
}}
129145
className={`${classes.root} ${className ?? ""}`}
130146
>
131-
{title !== "none" && (
147+
{title !== false && (
132148
<div className={classes.heading}>
133149
<CodeSampleTitle
134150
component={title}
@@ -143,9 +159,13 @@ export function CodeSamplesViewer({
143159
)}
144160
{state.status === "success" && operationIds.length > 1 && (
145161
<Selector
146-
value={state.selectedSnippet?.raw.operationId}
162+
value={getOperationKey(state.selectedSnippet?.raw)}
147163
values={operationIds}
148-
onChange={(operationId) => selectSnippet({ operationId })}
164+
onChange={(operationId: string) =>
165+
selectSnippet({
166+
methodPath: operationIdToMethodAndPath[operationId],
167+
})
168+
}
149169
className={classes.selector}
150170
/>
151171
)}
@@ -155,7 +175,7 @@ export function CodeSamplesViewer({
155175
state.selectedSnippet?.raw.language,
156176
)}
157177
values={languages}
158-
onChange={(language) => selectSnippet({ language })}
178+
onChange={(language: string) => selectSnippet({ language })}
159179
className={classes.selector}
160180
/>
161181
)}

src/react-components/titles.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,10 +81,13 @@ export const CodeSampleTitle: React.FC<TitleComponentProps> = (props) => {
8181

8282
if (props.status !== "success") return <TitleSkeleton />;
8383

84+
if (props.component === false) {
85+
return null;
86+
}
87+
8488
if (React.isValidElement(props.component)) return props.component;
8589

8690
if (typeof props.component === "string") {
87-
if (props.component === "none") return null;
8891
return <span>{props.component}</span>;
8992
}
9093

0 commit comments

Comments
 (0)