@stainless-api/docs-ui 0.1.0-beta.2 → 0.1.0-beta.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/dist/index.js +1312 -1871
  2. package/dist/mcp.cjs +983441 -0
  3. package/dist/routing.js +4 -4
  4. package/dist/styles/main.css +743 -747
  5. package/dist/styles/primitives.css +444 -426
  6. package/dist/styles/resets.css +33 -41
  7. package/dist/styles/search.css +265 -248
  8. package/dist/styles/sidebar.css +58 -60
  9. package/dist/styles/snippets.css +86 -88
  10. package/dist/styles/variables.css +85 -89
  11. package/package.json +19 -10
  12. package/src/components/breadcrumbs.tsx +1 -1
  13. package/src/components/chat.tsx +18 -15
  14. package/src/components/method.tsx +12 -11
  15. package/src/components/overview.tsx +32 -19
  16. package/src/components/primitives.tsx +36 -19
  17. package/src/components/properties.tsx +4 -2
  18. package/src/components/scripts/dropdown.ts +1 -1
  19. package/src/components/sdk.tsx +28 -22
  20. package/src/components/sidebar.tsx +3 -3
  21. package/src/components/snippets.tsx +29 -11
  22. package/src/contexts/component-generics.tsx +10 -15
  23. package/src/contexts/docs.tsx +15 -4
  24. package/src/contexts/index.tsx +8 -5
  25. package/src/contexts/markdown.tsx +7 -6
  26. package/src/contexts/search.tsx +4 -5
  27. package/src/hooks/use-strict-context.tsx +16 -0
  28. package/src/languages/go.tsx +3 -3
  29. package/src/languages/http.tsx +31 -23
  30. package/src/languages/index.ts +7 -7
  31. package/src/languages/java.tsx +4 -4
  32. package/src/languages/python.tsx +12 -9
  33. package/src/languages/ruby.tsx +20 -13
  34. package/src/languages/typescript.tsx +18 -12
  35. package/src/markdown/index.ts +17 -12
  36. package/src/markdown/utils.ts +6 -3
  37. package/src/routing.ts +9 -9
  38. package/src/search/form.tsx +11 -8
  39. package/src/search/indexer.ts +17 -15
  40. package/src/search/mcp.ts +108 -16
  41. package/src/search/printer.tsx +1 -1
  42. package/src/search/providers/algolia.ts +5 -5
  43. package/src/search/providers/fuse.ts +4 -4
  44. package/src/search/providers/pagefind.ts +1 -1
  45. package/src/search/providers/walker.ts +5 -3
  46. package/src/search/results.tsx +9 -7
  47. package/src/search/types.ts +2 -2
  48. package/src/style.ts +1 -1
  49. package/src/styles/main.css +743 -747
  50. package/src/styles/primitives.css +444 -426
  51. package/src/styles/resets.css +33 -41
  52. package/src/styles/search.css +265 -248
  53. package/src/styles/sidebar.css +58 -60
  54. package/src/styles/snippets.css +86 -88
  55. package/src/styles/variables.css +85 -89
  56. package/src/utils.ts +14 -15
  57. package/dist/mcp.js +0 -16003
@@ -18,23 +18,24 @@ export const HttpMethodIcons: Record<HTTPMethod, LucideIcon> = {
18
18
  };
19
19
 
20
20
  export type MethodIconProps = {
21
- httpMethod: string;
21
+ httpMethod?: string;
22
22
  showName?: boolean;
23
23
  };
24
24
 
25
25
  export function MethodIconBadge({ httpMethod, showName }: MethodIconProps) {
26
- if (!HttpMethods.includes(httpMethod)) return null;
26
+ if (!httpMethod || !HttpMethods.includes(httpMethod)) return null;
27
27
 
28
28
  return (
29
29
  <span
30
30
  className={clsx(style.MethodRouteHttpMethod, style.MethodRouteHttpMethodIconOnly)}
31
31
  data-method={httpMethod}
32
32
  >
33
- {React.createElement(HttpMethodIcons[httpMethod], {
34
- size: 14,
35
- strokeWidth: 3,
36
- className: style.Icon,
37
- })}
33
+ {HttpMethodIcons[httpMethod] &&
34
+ React.createElement(HttpMethodIcons[httpMethod], {
35
+ size: 14,
36
+ strokeWidth: 3,
37
+ className: style.Icon,
38
+ })}
38
39
  {showName && httpMethod}
39
40
  </span>
40
41
  );
@@ -42,15 +43,17 @@ export function MethodIconBadge({ httpMethod, showName }: MethodIconProps) {
42
43
 
43
44
  export type MethodHeaderProps = {
44
45
  title: ReactNode;
46
+ level?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5';
45
47
  signature?: ReactNode;
46
48
  badges?: ReactNode;
47
49
  children?: ReactNode;
48
50
  };
49
51
 
50
- export function MethodHeader({ title, badges, signature, children }: MethodHeaderProps) {
52
+ export function MethodHeader({ title, badges, signature, children, level }: MethodHeaderProps) {
53
+ const Heading = level ?? 'h5';
51
54
  return (
52
55
  <div className={style.MethodHeader}>
53
- <h5 className={style.MethodTitle}>{title}</h5>
56
+ <Heading className={style.MethodTitle}>{title}</Heading>
54
57
  {badges && <div className={style.MethodBadges}>{badges}</div>}
55
58
  {signature}
56
59
  {children}
@@ -133,8 +136,6 @@ export type MethodProps = {
133
136
  } & React.HTMLProps<HTMLDivElement>;
134
137
 
135
138
  export function Method({ id, header, children, className, ...props }: MethodProps) {
136
- const Docs = useComponents();
137
-
138
139
  return (
139
140
  <div id={id} className={clsx(style.Method, className)} tabIndex={0} {...props}>
140
141
  {header}
@@ -1,6 +1,6 @@
1
1
  import * as React from 'react';
2
2
  import { ChevronRight } from 'lucide-react';
3
- import type * as SDKJSON from '~/lib/json-spec-v2/types';
3
+ import type * as SDKJSON from '@stainless/sdk-json';
4
4
 
5
5
  import { flatResources, getResourceFromSpec } from '../utils';
6
6
  import { useDeclaration, useLanguage, useLanguageComponents, useSpec } from '../contexts';
@@ -54,10 +54,11 @@ export type SDKMethodSummaryProps = {
54
54
  export function SDKMethodSummary({ method }: SDKMethodSummaryProps) {
55
55
  const Docs = useComponents();
56
56
  const Lang = useLanguageComponents();
57
- const decl = useDeclaration(method.stainlessPath);
57
+ const decl = useDeclaration(method.stainlessPath, true);
58
58
 
59
59
  return (
60
60
  <Docs.MethodHeader
61
+ level="h5"
61
62
  title={<Docs.Link stainlessPath={method.stainlessPath}>{method.summary ?? method.title}</Docs.Link>}
62
63
  signature={<Lang.MethodSignature decl={decl} />}
63
64
  badges={method.deprecated && <Docs.Badge id="deprecated">Deprecated</Docs.Badge>}
@@ -75,11 +76,11 @@ export function SDKResource({ resource, parents, showModels }: SDKResourceProps
75
76
  const spec = useSpec();
76
77
 
77
78
  const methods = Object.values(resource.methods).filter(
78
- (method) => spec.decls?.[language]?.[method.stainlessPath],
79
+ (method) => spec?.decls?.[language]?.[method.stainlessPath],
79
80
  );
80
81
 
81
82
  const models = Object.values(resource.models).filter(
82
- (model) => spec.decls?.[language]?.[`${model.stainlessPath} > (schema)`],
83
+ (model) => spec?.decls?.[language]?.[`${model.stainlessPath} > (schema)`],
83
84
  );
84
85
 
85
86
  return (
@@ -88,13 +89,11 @@ export function SDKResource({ resource, parents, showModels }: SDKResourceProps
88
89
  <Docs.SDKResourceHeader resource={resource} parents={parents} />
89
90
  {methods.length > 0 && (
90
91
  <div className={style.ResourceContentGroup}>
91
- {methods
92
- .toSorted((first, second) => first.name.localeCompare(second.name))
93
- .map((method) => (
94
- <div className={style.MethodSummary} key={method.stainlessPath}>
95
- <Docs.SDKMethodSummary method={method} />
96
- </div>
97
- ))}
92
+ {methods.map((method) => (
93
+ <div className={style.MethodSummary} key={method.stainlessPath}>
94
+ <Docs.SDKMethodSummary method={method} />
95
+ </div>
96
+ ))}
98
97
  </div>
99
98
  )}
100
99
 
@@ -145,15 +144,29 @@ export function SDKRoot({ stainlessPath }: SDKRootProps) {
145
144
  const Docs = useComponents();
146
145
 
147
146
  const parsed = parseStainlessPath(stainlessPath);
148
- const resource = getResourceFromSpec(stainlessPath, spec);
147
+ const resource = spec && getResourceFromSpec(stainlessPath, spec);
149
148
 
150
- if (!resource || !parsed) return null;
149
+ if (!resource || !parsed) {
150
+ console.warn(`Could not find resource or parsed path for '${stainlessPath}'`);
151
+ return null;
152
+ }
151
153
 
152
- const content = parsed.method ? (
153
- <Docs.SDKMethod method={resource.methods[parsed.method]} />
154
- ) : (
155
- <Docs.SDKOverview resource={resource} />
156
- );
154
+ if (parsed.method) {
155
+ const method = resource.methods[parsed.method];
156
+ if (!method) {
157
+ console.warn(`Method '${parsed.method}' not found in resource '${resource.stainlessPath}'`);
158
+ return null;
159
+ }
160
+ return (
161
+ <div className={style.Root}>
162
+ <Docs.SDKMethod method={method} />
163
+ </div>
164
+ );
165
+ }
157
166
 
158
- return <div className={style.Root}>{content}</div>;
167
+ return (
168
+ <div className={style.Root}>
169
+ <Docs.SDKOverview resource={resource} />
170
+ </div>
171
+ );
159
172
  }
@@ -10,7 +10,12 @@ type JoinProps = { items: ReactNode[]; limit?: number; children: ReactNode };
10
10
  export function Join({ items, limit, children }: JoinProps) {
11
11
  const arr =
12
12
  limit && items.length > limit + 1
13
- ? [...items.slice(0, limit), <span className={style.Truncation}>{items.length - limit} more</span>]
13
+ ? [
14
+ ...items.slice(0, limit),
15
+ <span className={style.Truncation} key="truncation">
16
+ {items.length - limit} more
17
+ </span>,
18
+ ]
14
19
  : items;
15
20
 
16
21
  return arr.map((item, index) => (
@@ -31,7 +36,8 @@ type ExpanderProps = {
31
36
  };
32
37
 
33
38
  export function Expander({ id, open, summary, virtual, muted, children }: ExpanderProps) {
34
- const { virtualExpanders } = useSettings();
39
+ const settings = useSettings();
40
+ const virtualExpanders = settings?.virtualExpanders;
35
41
 
36
42
  if (virtual || virtualExpanders)
37
43
  return (
@@ -154,8 +160,8 @@ export function Link({ stainlessPath, scroll = true, children, ...props }: LinkP
154
160
 
155
161
  const href = React.useMemo(() => {
156
162
  if (props.href) return props.href;
157
- if (stainlessPath) return generateRoute(basePath, language, stainlessPath);
158
- }, [basePath, language, stainlessPath]);
163
+ if (stainlessPath && basePath) return generateRoute(basePath, language, stainlessPath);
164
+ }, [basePath, language, stainlessPath, props.href]);
159
165
 
160
166
  const handleClick = React.useCallback(
161
167
  (e: React.MouseEvent<HTMLAnchorElement>) => {
@@ -163,7 +169,7 @@ export function Link({ stainlessPath, scroll = true, children, ...props }: LinkP
163
169
  if (e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;
164
170
  if (href) onNavigate?.(e, { href, language, stainlessPath, scroll });
165
171
  },
166
- [href, scroll, onNavigate],
172
+ [href, scroll, onNavigate, language, props, stainlessPath],
167
173
  );
168
174
 
169
175
  if (!href) return children;
@@ -180,7 +186,10 @@ export type InputProps = {
180
186
  right?: ReactNode;
181
187
  } & React.InputHTMLAttributes<HTMLInputElement>;
182
188
 
183
- export const Input = React.forwardRef<HTMLInputElement, InputProps>(({ left, right, ...props }, ref) => {
189
+ export const Input = React.forwardRef<HTMLInputElement, InputProps>(function Input(
190
+ { left, right, ...props },
191
+ ref,
192
+ ) {
184
193
  return (
185
194
  <div className={style.Input}>
186
195
  {left}
@@ -195,15 +204,16 @@ export type ToggleButtonProps = {
195
204
  selected?: boolean;
196
205
  } & React.ButtonHTMLAttributes<HTMLButtonElement>;
197
206
 
198
- export const ToggleButton = React.forwardRef<HTMLButtonElement, ToggleButtonProps>(
199
- ({ children, selected, ...props }, ref) => {
200
- return (
201
- <button {...props} ref={ref} className={style.ToggleButton} data-stldocs-toggle-selected={selected}>
202
- {children}
203
- </button>
204
- );
205
- },
206
- );
207
+ export const ToggleButton = React.forwardRef<HTMLButtonElement, ToggleButtonProps>(function ToggleButton(
208
+ { children, selected, ...props },
209
+ ref,
210
+ ) {
211
+ return (
212
+ <button {...props} ref={ref} className={style.ToggleButton} data-stldocs-toggle-selected={selected}>
213
+ {children}
214
+ </button>
215
+ );
216
+ });
207
217
 
208
218
  export type ListViewProps<TItem> = {
209
219
  items: Array<TItem>;
@@ -222,6 +232,13 @@ export function ListView<TItem>({ items, itemDelegate, onSelectListItem, ...rest
222
232
  setKeyboardIndex(0);
223
233
  }, [items]);
224
234
 
235
+ const handleSelect = React.useCallback(() => {
236
+ const item = items[selectedIndex];
237
+ if (item) {
238
+ onSelectListItem?.(item);
239
+ }
240
+ }, [items, selectedIndex, onSelectListItem]);
241
+
225
242
  React.useEffect(() => {
226
243
  function handleKeyPress(ev: KeyboardEvent) {
227
244
  switch (ev.key) {
@@ -243,17 +260,17 @@ export function ListView<TItem>({ items, itemDelegate, onSelectListItem, ...rest
243
260
 
244
261
  case 'Enter':
245
262
  ev.preventDefault();
246
- onSelectListItem?.(items[selectedIndex]);
263
+ handleSelect();
247
264
  break;
248
265
  }
249
266
  }
250
267
 
251
268
  addEventListener('keydown', handleKeyPress);
252
269
  return () => removeEventListener('keydown', handleKeyPress);
253
- }, [items, selectedIndex]);
270
+ }, [items, selectedIndex, handleSelect]);
254
271
 
255
272
  React.useEffect(() => {
256
- if (!keyboardIndex) {
273
+ if (!keyboardIndex || !itemRef.current || !listRef.current) {
257
274
  listRef?.current?.scroll(0, 0);
258
275
  return;
259
276
  }
@@ -273,7 +290,7 @@ export function ListView<TItem>({ items, itemDelegate, onSelectListItem, ...rest
273
290
  ref={index === selectedIndex ? itemRef : null}
274
291
  className={style.ListViewItem}
275
292
  data-stldocs-listview-selected={index === selectedIndex}
276
- onClick={() => onSelectListItem?.(items[selectedIndex])}
293
+ onClick={handleSelect}
277
294
  onMouseMove={() => setSelectedIndex(index)}
278
295
  >
279
296
  {itemDelegate(item, index === selectedIndex)}
@@ -1,4 +1,4 @@
1
- import React, { type ReactNode } from 'react';
1
+ import { type ReactNode } from 'react';
2
2
  import { useLanguage, useSettings } from '../contexts';
3
3
  import { useComponents } from '../contexts/use-components';
4
4
  import style from '../style';
@@ -72,7 +72,9 @@ export function Property({
72
72
  const Docs = useComponents();
73
73
  const language = useLanguage();
74
74
 
75
- const { collapseDescription, types } = useSettings().properties ?? {};
75
+ const properties = useSettings()?.properties;
76
+ const collapseDescription = properties?.collapseDescription;
77
+ const types = properties?.types;
76
78
 
77
79
  const textContent = (
78
80
  <>
@@ -46,7 +46,7 @@ export function initDropdown({
46
46
  }
47
47
 
48
48
  // Toggle dropdown on button click
49
- button.addEventListener('click', (e) => {
49
+ button.addEventListener('click', () => {
50
50
  toggleDropdown();
51
51
  });
52
52
 
@@ -1,5 +1,5 @@
1
1
  import * as React from 'react';
2
- import type * as SDKJSON from '~/lib/json-spec-v2/types';
2
+ import type * as SDKJSON from '@stainless/sdk-json';
3
3
  import { useComponents } from '../contexts/use-components';
4
4
  import {
5
5
  useLanguageComponents,
@@ -32,7 +32,7 @@ export type PropertyModelContextType = {
32
32
  propertyPath?: string;
33
33
  };
34
34
 
35
- export const PropertyModelContext = React.createContext<PropertyModelContextType>(null);
35
+ export const PropertyModelContext = React.createContext<PropertyModelContextType>({});
36
36
  export function usePropertyModel() {
37
37
  return React.useContext(PropertyModelContext);
38
38
  }
@@ -93,16 +93,15 @@ type SDKDeclarationProps = {
93
93
  export function SDKDeclaration({ path, expand, depth = 0 }: SDKDeclarationProps) {
94
94
  const Lang = useLanguageComponents();
95
95
  const Docs = useComponents();
96
- const decl = useDeclaration(path);
96
+ const decl = useDeclaration(path, true);
97
97
  const settings = useSettings();
98
98
  const model = usePropertyModel();
99
99
  const nesting = useReferenceNesting();
100
100
  const { selectedPath } = useNavigation();
101
101
 
102
- if (!decl) return null;
103
-
104
102
  if (decl.kind.endsWith('Reference')) {
105
- const refId = decl['type']['$ref'];
103
+ const refId =
104
+ 'type' in decl && typeof decl.type !== 'string' && '$ref' in decl.type ? decl.type['$ref'] : undefined;
106
105
  if (refId && refId !== path && !nesting.includes(refId)) {
107
106
  return (
108
107
  <ReferenceNestingContext.Provider value={[...nesting, refId]}>
@@ -112,11 +111,14 @@ export function SDKDeclaration({ path, expand, depth = 0 }: SDKDeclarationProps)
112
111
  }
113
112
  }
114
113
 
115
- const isUnion = 'childrenParentSchema' in decl && ['enum', 'union'].includes(decl.childrenParentSchema);
114
+ const isUnion =
115
+ 'childrenParentSchema' in decl &&
116
+ decl.childrenParentSchema &&
117
+ ['enum', 'union'].includes(decl.childrenParentSchema);
116
118
  const id = model?.propertyPath ? `${model.propertyPath} + ${path}` : path;
117
119
  const shouldExpand =
118
- (selectedPath.startsWith(path) && nesting.length < 1) ||
119
- (settings.properties?.expandDepth && depth <= settings.properties?.expandDepth && !isUnion) ||
120
+ (selectedPath?.startsWith(path) && nesting.length < 1) ||
121
+ (settings?.properties?.expandDepth && depth <= settings?.properties?.expandDepth && !isUnion) ||
120
122
  expand;
121
123
 
122
124
  const content = (
@@ -125,18 +127,18 @@ export function SDKDeclaration({ path, expand, depth = 0 }: SDKDeclarationProps)
125
127
  <Docs.Property
126
128
  id={id}
127
129
  expand={shouldExpand}
128
- constraints={decl['constraints'] && <Docs.SDKConstraints constraints={decl['constraints']} />}
130
+ constraints={'constraints' in decl && <Docs.SDKConstraints constraints={decl['constraints']} />}
129
131
  declaration={<Lang.Declaration decl={decl} />}
130
- description={decl['docstring']}
132
+ description={'docstring' in decl ? decl['docstring'] : undefined}
131
133
  deprecated={decl.deprecated}
132
134
  {...props}
133
135
  >
134
136
  {'children' in decl &&
135
- decl.children.length > 0 &&
136
- (settings.properties?.includeModelProperties !== false || !('modelPath' in decl)) && (
137
+ (decl.children?.length ?? 0) > 0 &&
138
+ (settings?.properties?.includeModelProperties !== false || !('modelPath' in decl)) && (
137
139
  <>
138
140
  {isUnion && <div className={style.PropertyAnnotation}>Accepts one of the following:</div>}
139
- <Docs.SDKChildren paths={decl.children} depth={depth + 1} />
141
+ <Docs.SDKChildren paths={decl.children ?? []} depth={depth + 1} />
140
142
  </>
141
143
  )}
142
144
  </Docs.Property>
@@ -239,10 +241,11 @@ export type SDKMethodProps = {
239
241
  export function SDKMethodHeader({ method }: SDKMethodProps) {
240
242
  const Docs = useComponents();
241
243
  const Lang = useLanguageComponents();
242
- const decl = useDeclaration(method.stainlessPath);
244
+ const decl = useDeclaration(method.stainlessPath, true);
243
245
 
244
246
  return (
245
247
  <Docs.MethodHeader
248
+ level="h1"
246
249
  title={method.summary ?? method.title}
247
250
  signature={<Lang.MethodSignature decl={decl} />}
248
251
  badges={method.deprecated && <Docs.Badge id="deprecated">Deprecated</Docs.Badge>}
@@ -255,7 +258,7 @@ export function SDKMethodHeader({ method }: SDKMethodProps) {
255
258
  export function SDKMethodInfo({ method }: SDKMethodProps) {
256
259
  const Docs = useComponents();
257
260
  const Lang = useLanguageComponents();
258
- const decl = useDeclaration(method.stainlessPath);
261
+ const decl = useDeclaration(method.stainlessPath, true);
259
262
  const spec = useSpec();
260
263
  const language = useLanguage();
261
264
 
@@ -263,8 +266,10 @@ export function SDKMethodInfo({ method }: SDKMethodProps) {
263
266
 
264
267
  function shouldExpand(items: SDKJSON.ID[]) {
265
268
  if (items.length > 1) return false;
266
- const decl = spec?.decls?.[language]?.[items[0]];
267
- return decl && 'children' in decl && decl.children.length > 0;
269
+ const item = items[0];
270
+ if (!item) return false;
271
+ const decl = spec?.decls?.[language]?.[item];
272
+ return decl && 'children' in decl && decl.children && decl.children.length > 0;
268
273
  }
269
274
 
270
275
  return (
@@ -278,6 +283,7 @@ export function SDKMethodInfo({ method }: SDKMethodProps) {
278
283
  }
279
284
  returns={
280
285
  'responseChildren' in decl &&
286
+ decl.responseChildren &&
281
287
  decl.responseChildren.length > 0 && (
282
288
  <Docs.SDKChildren expand={shouldExpand(decl.responseChildren)} paths={decl.responseChildren} />
283
289
  )
@@ -288,7 +294,7 @@ export function SDKMethodInfo({ method }: SDKMethodProps) {
288
294
 
289
295
  export function SDKMethod({ method, transformRequestSnippet }: SDKMethodProps) {
290
296
  const Docs = useComponents();
291
- const decl = useDeclaration(method?.stainlessPath);
297
+ const decl = useDeclaration(method?.stainlessPath, true);
292
298
  const layout = useContentPanelLayout();
293
299
 
294
300
  if (!decl) return;
@@ -323,7 +329,7 @@ export type SDKModelProps = {
323
329
 
324
330
  export function SDKModel({ model }: SDKModelProps) {
325
331
  const Docs = useComponents();
326
- const decl = useDeclaration(`${model.stainlessPath} > (schema)`);
332
+ const decl = useDeclaration(`${model.stainlessPath} > (schema)`, true);
327
333
 
328
334
  if (!decl) return null;
329
335
 
@@ -383,9 +389,9 @@ export function SDKLanguageBlock({ language, version, install, links }: SDKLangu
383
389
 
384
390
  <div className={style.LanguageBlockInstall} data-stldocs-copy-parent>
385
391
  <pre data-stldocs-copy-content>{install}</pre>{' '}
386
- <button data-stldocs-snippet-copy>
392
+ <Button variant="ghost" size="sm" data-stldocs-snippet-copy>
387
393
  <Copy size={16} className={style.Icon} />
388
- </button>
394
+ </Button>
389
395
  </div>
390
396
 
391
397
  <div className={style.LanguageBlockLinks}>
@@ -1,5 +1,5 @@
1
1
  import * as React from 'react';
2
- import type * as SDKJSON from '~/lib/json-spec-v2/types';
2
+ import type * as SDKJSON from '@stainless/sdk-json';
3
3
  import { useNavigation } from '../contexts';
4
4
  import { useComponents } from '../contexts/use-components';
5
5
  import { ChevronDown, ChevronRight } from 'lucide-react';
@@ -50,8 +50,8 @@ export type SidebarResourceProps = {
50
50
 
51
51
  export function SidebarResource({ resource }: SidebarResourceProps) {
52
52
  const Docs = useComponents();
53
- const { selectedPath, navigationPath } = useNavigation();
54
- const subresources = Object.values(resource.subresources).map((sub) => (
53
+ const { selectedPath } = useNavigation();
54
+ const subresources = Object.values(resource.subresources ?? {}).map((sub) => (
55
55
  <SidebarResource resource={sub} key={sub.stainlessPath} />
56
56
  ));
57
57
 
@@ -4,8 +4,9 @@ import { useDeclaration, useHighlight, useLanguage, useSnippet } from '../contex
4
4
  import { useComponents } from '../contexts/use-components';
5
5
  import style from '../style';
6
6
  import clsx from 'clsx';
7
- import type * as SDKJSON from '~/lib/json-spec-v2/types';
7
+ import type * as SDKJSON from '@stainless/sdk-json';
8
8
  import type { TransformRequestSnippetFn } from './sdk';
9
+ import { Button } from '@stainless-api/ui-primitives';
9
10
 
10
11
  export type SnippetCodeProps = {
11
12
  content: string;
@@ -58,15 +59,24 @@ export function Snippet({ requestTitle, method, transformRequestSnippet }: Snipp
58
59
  const language = useLanguage();
59
60
  const [CopyButtonIcon, setCopyIcon] = React.useState<LucideIcon>(CopyIcon);
60
61
 
61
- let snippet = useSnippet(method.stainlessPath, null, language === 'http' ? 'curl' : 'default');
62
- const decl = useDeclaration(method.stainlessPath);
62
+ const originalSnippet = useSnippet(
63
+ method.stainlessPath,
64
+ undefined,
65
+ language === 'http' ? 'curl' : 'default',
66
+ );
67
+ const decl = useDeclaration(method.stainlessPath, false);
68
+
69
+ if (!originalSnippet) {
70
+ console.warn(`Snippet not found for method '${method.stainlessPath}'`);
71
+ return null;
72
+ }
63
73
 
64
- const signature = 'qualified' in decl ? decl.qualified : undefined;
74
+ const signature = decl && 'qualified' in decl ? decl.qualified : undefined;
65
75
  const responses = method.exampleResponses;
66
76
 
67
- if (transformRequestSnippet) {
68
- snippet = transformRequestSnippet({ snippet, language });
69
- }
77
+ const snippet = transformRequestSnippet
78
+ ? transformRequestSnippet({ snippet: originalSnippet, language })
79
+ : originalSnippet;
70
80
 
71
81
  async function handleCopy() {
72
82
  try {
@@ -90,9 +100,9 @@ export function Snippet({ requestTitle, method, transformRequestSnippet }: Snipp
90
100
  <h5>{method.summary}</h5>
91
101
  </div>
92
102
  <div className={style.SnippetRequestTitleContent}>{requestTitle}</div>
93
- <button data-stldocs-snippet-copy className={style.SnippetRequestTitleCopyButton}>
103
+ <Button variant="ghost" data-stldocs-snippet-copy>
94
104
  <CopyButtonIcon size={16} className={style.Icon} onClick={handleCopy} />
95
- </button>
105
+ </Button>
96
106
  </div>
97
107
  <Docs.SnippetCode content={snippet} signature={signature} />
98
108
  </div>
@@ -112,9 +122,15 @@ export function SnippetResponse({ responses }: { responses: SDKJSON.Method['exam
112
122
 
113
123
  const mappedResponses = Object.keys(responses)
114
124
  .map((key) => {
125
+ const responseContent = responses[key];
126
+ if (!responseContent) return null;
127
+
115
128
  // Get the first response type ie application/json or text/plain
116
- const responseType = Object.keys(responses[key])[0];
117
- const response = responses[key][responseType];
129
+ const responseType = Object.keys(responseContent)[0];
130
+ if (!responseType) return null;
131
+
132
+ const response = responseContent[responseType];
133
+ if (!response) return null;
118
134
 
119
135
  const examples = response?.examples;
120
136
 
@@ -122,6 +138,8 @@ export function SnippetResponse({ responses }: { responses: SDKJSON.Method['exam
122
138
 
123
139
  // Get the first example type, ie Example or html
124
140
  const exampleType = Object.keys(examples)[0];
141
+ if (!exampleType) return null;
142
+
125
143
  let value = examples[exampleType]?.value;
126
144
 
127
145
  if (!value) return null;
@@ -1,6 +1,7 @@
1
1
  // This file should never import from ../components or ../languages to avoid circular dependencies
2
2
 
3
3
  import * as React from 'react';
4
+ import { createStrictContext } from '../hooks/use-strict-context';
4
5
 
5
6
  type DeepPartialMap<L> = { [K in keyof L]?: Partial<L[K]> };
6
7
 
@@ -9,13 +10,10 @@ export type ComponentsContextType<C, L> = {
9
10
  language: L;
10
11
  };
11
12
 
12
- const ComponentContext = React.createContext<ComponentsContextType<any, any> | null>(null);
13
- ComponentContext.displayName = 'ComponentContext';
13
+ const [Provider, useComponentContext] = createStrictContext<ComponentsContextType<any, any>>('Component');
14
14
 
15
- export function useComponents<C = unknown>() {
16
- const ctx = React.useContext(ComponentContext);
17
- if (!ctx) throw new Error('useComponents must be used within a ComponentContext.Provider');
18
- return ctx.components as C;
15
+ export function useComponents<C = unknown>(): C {
16
+ return useComponentContext().components as C;
19
17
  }
20
18
 
21
19
  export function customizeComponents<C, L>(
@@ -24,13 +22,10 @@ export function customizeComponents<C, L>(
24
22
  ): { components: C; language: L } {
25
23
  const mergedComponents = { ...defaults.components, ...(overrides.components ?? {}) } as C;
26
24
 
27
- const mergedLanguage = Object.keys(defaults.language as Record<string, unknown>).reduce(
28
- (acc, key) => {
29
- acc[key] = { ...(defaults.language as any)[key], ...(overrides.language?.[key] ?? {}) };
30
- return acc;
31
- },
32
- {} as any as L,
33
- );
25
+ const mergedLanguage = Object.keys(defaults.language as Record<string, unknown>).reduce((acc, key) => {
26
+ acc[key] = { ...(defaults.language as any)[key], ...((overrides.language as any)?.[key] ?? {}) };
27
+ return acc;
28
+ }, {} as any);
34
29
 
35
30
  return { components: mergedComponents, language: mergedLanguage };
36
31
  }
@@ -42,7 +37,7 @@ export function ComponentProvider<C, L>({
42
37
  value: ComponentsContextType<C, L>;
43
38
  children: React.ReactNode;
44
39
  }) {
45
- return <ComponentContext.Provider value={value}>{children}</ComponentContext.Provider>;
40
+ return <Provider value={value}>{children}</Provider>;
46
41
  }
47
42
 
48
- export { ComponentContext };
43
+ export { useComponentContext };
@@ -1,5 +1,5 @@
1
1
  import * as React from 'react';
2
- import type { Spec, SpecLanguage } from '~/lib/json-spec-v2/types';
2
+ import type { LanguageDeclNodes, SnippetLanguage, Spec, SpecLanguage } from '@stainless/sdk-json';
3
3
 
4
4
  export type ContentPanelLayout = 'double-pane' | 'single-pane';
5
5
  export type PropertySettingsType = {
@@ -40,13 +40,24 @@ export function useSettings() {
40
40
  }
41
41
 
42
42
  export function useSnippet(stainlessPath: string, language?: SpecLanguage, variant?: string) {
43
- const snippetLanguage = [language ?? useLanguage(), variant ?? 'default'].join('.');
43
+ const defaultLanguage = useLanguage();
44
+ const snippetLanguage = [language ?? defaultLanguage, variant ?? 'default'].join('.') as SnippetLanguage;
44
45
 
45
46
  return useSpec()?.snippets?.[snippetLanguage]?.[stainlessPath];
46
47
  }
47
48
 
48
- export function useDeclaration(stainlessPath: string, language?: SpecLanguage) {
49
- return useSpec()?.decls?.[language ?? useLanguage()]?.[stainlessPath];
49
+ type Declaration = LanguageDeclNodes[SpecLanguage];
50
+ export function useDeclaration<Required extends boolean>(
51
+ stainlessPath: string,
52
+ required: Required,
53
+ language?: SpecLanguage,
54
+ ): (Required extends true ? never : undefined) | Declaration {
55
+ const defaultLanguage = useLanguage();
56
+ const decl = useSpec()?.decls?.[language ?? defaultLanguage]?.[stainlessPath];
57
+ if (required && !decl) {
58
+ throw new Error(`Declaration not found for '${stainlessPath}'`);
59
+ }
60
+ return decl!;
50
61
  }
51
62
 
52
63
  export function useResource(name: string) {
@@ -1,7 +1,6 @@
1
- import * as React from 'react';
2
- import { ComponentContext } from './component-generics';
3
1
  import { useLanguage } from './docs';
4
2
  import { LanguageComponentDefinition } from '../languages';
3
+ import { useComponentContext } from './component-generics';
5
4
 
6
5
  // DO NOT re-export component contexts from here. Only export generics.
7
6
  export * from './navigation';
@@ -10,8 +9,12 @@ export * from './component-generics';
10
9
  export * from './search';
11
10
  export * from './docs';
12
11
 
13
- export function useLanguageComponents() {
12
+ export function useLanguageComponents(): LanguageComponentDefinition {
14
13
  const language = useLanguage();
15
- const context = React.useContext(ComponentContext);
16
- return context.language[language] as LanguageComponentDefinition;
14
+ const context = useComponentContext();
15
+ const definition = context.language[language];
16
+ if (!definition) {
17
+ throw new Error(`Language component definition not found for language: ${language}`);
18
+ }
19
+ return definition;
17
20
  }