Skip to content

Commit 41a7d8f

Browse files
feat: support operationId selection out of the box
1 parent 5419db6 commit 41a7d8f

File tree

9 files changed

+259
-193
lines changed

9 files changed

+259
-193
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,6 @@
2020
/__tests__
2121
/.speakeasy/reports
2222
/react.*
23+
.idea
24+
pnpm-lock.yaml
25+
*.iml
Lines changed: 57 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
import { HighlightedCode } from "codehike/code";
1+
import {HighlightedCode} from "codehike/code";
22
import React from "react";
3-
import { SpeakeasyCodeSamplesCore } from "../core.js";
4-
import { codeSamplesGet } from "../funcs/codeSamplesGet.js";
5-
import { UsageSnippet } from "../models/components/usagesnippet.js";
6-
import { GetCodeSamplesRequest } from "../models/operations/getcodesamples.js";
7-
import { useSpeakeasyCodeSamplesContext } from "../react-query/_context.js";
8-
import { highlightCode } from "./utils.js";
3+
import {SpeakeasyCodeSamplesCore} from "../core.js";
4+
import {codeSamplesGet} from "../funcs/codeSamplesGet.js";
5+
import {UsageSnippet} from "../models/components/usagesnippet.js";
6+
import {GetCodeSamplesRequest} from "../models/operations/getcodesamples.js";
7+
import {useSpeakeasyCodeSamplesContext} from "../react-query/_context.js";
8+
import {highlightCode} from "./utils.js";
99

1010
export type SpeakeasyHighlightedCode = HighlightedCode & {
1111
/** The snippet data from the code samples api */
@@ -15,23 +15,23 @@ export type SpeakeasyHighlightedCode = HighlightedCode & {
1515
// Define the state shape.
1616
export type CodeSampleState =
1717
| {
18-
status: "loading";
19-
error?: Error | undefined;
20-
snippets?: SpeakeasyHighlightedCode[] | undefined;
21-
selectedSnippet?: SpeakeasyHighlightedCode | undefined;
22-
}
18+
status: "loading";
19+
error?: Error | undefined;
20+
snippets?: SpeakeasyHighlightedCode[] | undefined;
21+
selectedSnippet?: SpeakeasyHighlightedCode | undefined;
22+
}
2323
| {
24-
status: "success";
25-
error?: Error | undefined;
26-
snippets: SpeakeasyHighlightedCode[];
27-
selectedSnippet: SpeakeasyHighlightedCode;
28-
}
24+
status: "success";
25+
error?: Error | undefined;
26+
snippets: SpeakeasyHighlightedCode[];
27+
selectedSnippet: SpeakeasyHighlightedCode;
28+
}
2929
| {
30-
status: "error";
31-
error: Error;
32-
snippets?: SpeakeasyHighlightedCode[] | undefined;
33-
selectedSnippet?: SpeakeasyHighlightedCode | undefined;
34-
};
30+
status: "error";
31+
error: Error;
32+
snippets?: SpeakeasyHighlightedCode[] | undefined;
33+
selectedSnippet?: SpeakeasyHighlightedCode | undefined;
34+
};
3535

3636
type FetchSuccessPayload = {
3737
snippets: SpeakeasyHighlightedCode[];
@@ -43,21 +43,27 @@ type Action =
4343
| { type: "FETCH_INIT" }
4444
| { type: "FETCH_SUCCESS"; payload: FetchSuccessPayload }
4545
| { type: "FETCH_FAILURE"; payload: Error }
46-
| { type: "SET_LANGUAGE"; payload: string };
46+
| { type: "SELECT"; payload: SafeGetSnippetParams }
4747

48-
function safeGetSnippetForLanguage(
48+
49+
type SafeGetSnippetParams = {
50+
operationId?: string | undefined;
51+
language?: string | undefined;
52+
};
53+
54+
function safeGetSnippet(
4955
snippets: SpeakeasyHighlightedCode[],
50-
language?: string,
56+
{operationId, language}: SafeGetSnippetParams,
5157
): SpeakeasyHighlightedCode {
52-
if (!language) return snippets[0]!;
58+
const maybeEqual = (a: string, b: string | undefined) => b === undefined || a.toLowerCase() === b.toLowerCase();
5359

54-
const selectedSnippet = snippets.find((s) => s.lang === language);
60+
const selectedSnippet = snippets.find((s) => maybeEqual(s.raw.operationId, operationId) && maybeEqual(s.lang, language));
5561
if (selectedSnippet) {
5662
return selectedSnippet;
5763
}
5864

5965
console.warn(
60-
`Could not find snippet for language "${language}".`,
66+
`Could not find snippet for operationId "${operationId}".`,
6167
`Falling back to to first language in snippet array.`,
6268
);
6369

@@ -70,14 +76,14 @@ const reducer: React.Reducer<CodeSampleState, Action> = (
7076
) => {
7177
switch (action.type) {
7278
case "FETCH_INIT":
73-
return { ...state, status: "loading" };
79+
return {...state, status: "loading"};
7480
case "FETCH_SUCCESS":
7581
return {
7682
status: "success",
7783
snippets: action.payload.snippets,
78-
selectedSnippet: safeGetSnippetForLanguage(
84+
selectedSnippet: safeGetSnippet(
7985
action.payload.snippets,
80-
action.payload.defaultLanguage,
86+
{language: action.payload.defaultLanguage},
8187
),
8288
};
8389
case "FETCH_FAILURE":
@@ -86,10 +92,10 @@ const reducer: React.Reducer<CodeSampleState, Action> = (
8692
status: "error",
8793
error: action.payload,
8894
};
89-
case "SET_LANGUAGE":
95+
case "SELECT":
9096
return {
9197
...state,
92-
selectedSnippet: safeGetSnippetForLanguage(
98+
selectedSnippet: safeGetSnippet(
9399
state.snippets!,
94100
action.payload,
95101
),
@@ -106,11 +112,11 @@ type UseCodeSampleStateInit = {
106112
};
107113

108114
export const useCodeSampleState = ({
109-
client: clientProp,
110-
requestParams,
111-
defaultLanguage,
112-
}: UseCodeSampleStateInit) => {
113-
const [state, dispatch] = React.useReducer(reducer, { status: "loading" });
115+
client: clientProp,
116+
requestParams,
117+
defaultLanguage,
118+
}: UseCodeSampleStateInit) => {
119+
const [state, dispatch] = React.useReducer(reducer, {status: "loading"});
114120
const client = useSafeSpeakeasyCodeSamplesContext(clientProp);
115121

116122
const highlightSnippets = async (snippets: UsageSnippet[]) => {
@@ -122,17 +128,17 @@ export const useCodeSampleState = ({
122128
"github-from-css",
123129
);
124130

125-
return { ...highlightedCode, raw: snippet };
131+
return {...highlightedCode, raw: snippet};
126132
}),
127133
);
128134
};
129135

130136
async function handleMount() {
131-
dispatch({ type: "FETCH_INIT" });
137+
dispatch({type: "FETCH_INIT"});
132138
const result = await codeSamplesGet(client, requestParams);
133139

134140
if (!result.ok) {
135-
return dispatch({ type: "FETCH_FAILURE", payload: result.error });
141+
return dispatch({type: "FETCH_FAILURE", payload: result.error});
136142
}
137143

138144
dispatch({
@@ -148,11 +154,17 @@ export const useCodeSampleState = ({
148154
handleMount();
149155
}, []);
150156

151-
function setSelectedLanguage(language: string) {
152-
dispatch({ type: "SET_LANGUAGE", payload: language });
157+
function selectSnippet(params: SafeGetSnippetParams) {
158+
dispatch({
159+
type: "SELECT", payload: {
160+
language: state.selectedSnippet?.raw.language,
161+
operationId: state.selectedSnippet?.raw.operationId,
162+
...params,
163+
}
164+
});
153165
}
154166

155-
return { state, setSelectedLanguage };
167+
return {state, selectSnippet};
156168
};
157169

158170
/** Intended to give the user the option of providing their own client. */
@@ -169,7 +181,7 @@ export const useSafeSpeakeasyCodeSamplesContext = (
169181
} catch {
170182
throw new Error(
171183
"The Speakeasy Code Samples component must either be given an apiKey and " +
172-
"registryUrl, or be wrapped in a SpeakeasyCodeSamplesProvider.",
184+
"registryUrl, or be wrapped in a SpeakeasyCodeSamplesProvider.",
173185
);
174186
}
175187
};

src/react-components/code-sample.styles.ts

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { css } from "@emotion/css";
2-
import { cssVarKey } from "./styles.js";
1+
import {css} from "@emotion/css";
2+
import {cssVarKey} from "./styles.js";
33

44
const classes = {
55
root: css({
@@ -14,9 +14,23 @@ const classes = {
1414
display: "flex",
1515
justifyContent: "space-between",
1616
borderBottom: `1px solid var(${cssVarKey.border})`,
17-
padding: "0.5rem",
17+
padding: "0.5rem 1rem",
18+
}),
19+
selector: css({
20+
padding: "0.5rem 0.25rem 0.5rem 0.5rem", // Less padding on the right side to account for the arrow
21+
borderRadius: "0.25rem",
22+
backgroundColor: `var(${cssVarKey.bgPrimary})`,
23+
fontSize: "0.875rem",
24+
border: `1px solid var(${cssVarKey.border})`,
25+
cursor: "pointer",
26+
transition: "all 0.2s ease",
27+
"&:hover": {
28+
backgroundColor: `var(${cssVarKey.bgMuted})`,
29+
},
30+
"&::-webkit-select-arrow": {
31+
color: `red`, // For WebKit browsers
32+
}
1833
}),
19-
selector: css({}),
2034
codeContainer: css({
2135
position: "relative",
2236
paddingInline: "0.75rem",

0 commit comments

Comments
 (0)