@stainless-api/docs 0.1.0-beta.44 → 0.1.0-beta.45

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # @stainless-api/docs
2
2
 
3
+ ## 0.1.0-beta.45
4
+
5
+ ### Patch Changes
6
+
7
+ - 00b33d9: syntax highlighting for markdown renderer
8
+ - fa43cff: json brackets should use muted foreground color
9
+ - c06e5c3: update marked dependency
10
+ - 32d1735: experimental support for collapsible method descriptions
11
+ - Updated dependencies [00b33d9]
12
+ - Updated dependencies [32d1735]
13
+ - Updated dependencies [23254d1]
14
+ - @stainless-api/ui-primitives@0.1.0-beta.26
15
+ - @stainless-api/docs-ui@0.1.0-beta.37
16
+
3
17
  ## 0.1.0-beta.44
4
18
 
5
19
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stainless-api/docs",
3
- "version": "0.1.0-beta.44",
3
+ "version": "0.1.0-beta.45",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -26,21 +26,20 @@
26
26
  "peerDependencies": {
27
27
  "@astrojs/starlight": ">=0.36.1",
28
28
  "astro": ">=5.15.3",
29
- "vite": ">=6.2.1",
30
29
  "react": ">=19.0.0",
31
- "react-dom": ">=19.0.0"
30
+ "react-dom": ">=19.0.0",
31
+ "vite": ">=6.2.1"
32
32
  },
33
33
  "dependencies": {
34
34
  "@astrojs/markdown-remark": "^6.3.9",
35
35
  "@astrojs/react": "^4.4.2",
36
- "@stainless-api/sdk": "0.1.0-alpha.12",
36
+ "@stainless-api/sdk": "0.1.0-alpha.16",
37
37
  "cheerio": "^1.1.2",
38
38
  "clsx": "^2.1.1",
39
39
  "dotenv": "17.2.3",
40
40
  "get-port": "^7.1.0",
41
- "highlight.js": "^11.11.1",
42
41
  "lucide-react": "^0.554.0",
43
- "marked": "^16.4.2",
42
+ "marked": "^17.0.1",
44
43
  "node-html-parser": "^7.0.1",
45
44
  "rehype-parse": "^9.0.1",
46
45
  "rehype-remark": "^10.0.1",
@@ -51,8 +50,8 @@
51
50
  "unified": "^11.0.5",
52
51
  "web-worker": "^1.5.0",
53
52
  "yaml": "^2.8.1",
54
- "@stainless-api/docs-ui": "0.1.0-beta.36",
55
- "@stainless-api/ui-primitives": "0.1.0-beta.25"
53
+ "@stainless-api/docs-ui": "0.1.0-beta.37",
54
+ "@stainless-api/ui-primitives": "0.1.0-beta.26"
56
55
  },
57
56
  "devDependencies": {
58
57
  "@astrojs/check": "^0.9.5",
@@ -62,7 +61,7 @@
62
61
  "@types/react-dom": "^19.2.3",
63
62
  "react": "^19.2.0",
64
63
  "react-dom": "^19.2.0",
65
- "tsx": "^4.20.3",
64
+ "tsx": "^4.20.6",
66
65
  "typescript": "5.9.3",
67
66
  "vite": "^6.4.1",
68
67
  "zod": "^4.1.12",
@@ -0,0 +1,54 @@
1
+ import { MethodDescriptionProps } from '@stainless-api/docs-ui/components';
2
+ import { useComponents } from '@stainless-api/docs-ui/contexts/use-components';
3
+ import style from '@stainless-api/docs-ui/style';
4
+ import { Button } from '@stainless-api/ui-primitives';
5
+
6
+ function shouldCollapseDescription(description: string) {
7
+ const MIN_CHARS = 400;
8
+ const MIN_LINES = 6;
9
+
10
+ const lineCount = description.split('\n').length;
11
+
12
+ if (description.length >= MIN_CHARS) return true;
13
+ if (lineCount >= MIN_LINES) return true;
14
+
15
+ // Markdown structure often means longer content
16
+ if (/#\s/.test(description)) return true; // has headings
17
+ if (/```/.test(description)) return true; // has code blocks
18
+ if (/^\s*[-*]\s+/m.test(description)) return true; // has lists
19
+
20
+ return false;
21
+ }
22
+
23
+ export function MethodDescription({ description }: MethodDescriptionProps) {
24
+ const { Markdown } = useComponents();
25
+
26
+ if (description) {
27
+ // Attempt to determine if we should make the description collapsible initially
28
+ // or not. If we get this right, there will be no FOUC.
29
+ const collapsible = shouldCollapseDescription(description);
30
+
31
+ return (
32
+ <div className="stl-method-description">
33
+ <div
34
+ className={style.MethodDescription}
35
+ data-stldocs-property-group="method-description"
36
+ data-collapsed={collapsible ? 'true' : 'false'}
37
+ >
38
+ <Markdown content={description} />
39
+ </div>
40
+ <div className="stl-method-description-overflow-wrapper">
41
+ <Button
42
+ type="button"
43
+ data-method-description-toggle
44
+ size="sm"
45
+ variant="ghost"
46
+ hidden={!collapsible}
47
+ >
48
+ Show more
49
+ </Button>
50
+ </div>
51
+ </div>
52
+ );
53
+ }
54
+ }
@@ -38,7 +38,10 @@ async function createMarkdownRenderer(): Promise<MarkdownContext> {
38
38
  transform(node, config) {
39
39
  const attributes = node.transformAttributes(config);
40
40
  const lang = node.attributes.language === 'node' ? 'typescript' : node.attributes.language;
41
- const code = highlighter.codeToTokens(node.attributes.content, { lang, theme: 'github-dark' });
41
+ const code = highlighter.codeToTokens(node.attributes.content, {
42
+ lang,
43
+ theme: 'github-dark',
44
+ });
42
45
 
43
46
  return new Markdoc.Tag(
44
47
  'pre',
@@ -0,0 +1,33 @@
1
+ document.addEventListener('DOMContentLoaded', function () {
2
+ const COLLAPSED_HEIGHT = 170;
3
+
4
+ const container = document.querySelector<HTMLElement>('[data-stldocs-property-group="method-description"]');
5
+
6
+ if (!container) return;
7
+
8
+ const toggle = document?.querySelector<HTMLButtonElement>('[data-method-description-toggle]');
9
+ if (!toggle) return;
10
+
11
+ // If content isn't tall enough, don't show the button
12
+ if (container.scrollHeight <= COLLAPSED_HEIGHT + 1) {
13
+ toggle.hidden = true;
14
+ container.dataset.collapsed = 'false';
15
+ return;
16
+ }
17
+
18
+ // Only show button if content is taller than collapsed max height
19
+ if (container.scrollHeight > COLLAPSED_HEIGHT + 1) {
20
+ toggle.hidden = false;
21
+ } else {
22
+ // Not tall enough to need collapsing — show full content and hide button
23
+ container.dataset.collapsed = 'false';
24
+ toggle.hidden = true;
25
+ return;
26
+ }
27
+
28
+ toggle.addEventListener('click', function () {
29
+ const isCollapsed = container.dataset.collapsed !== 'false';
30
+ container.dataset.collapsed = isCollapsed ? 'false' : 'true';
31
+ toggle.textContent = isCollapsed ? 'Show less' : 'Show more';
32
+ });
33
+ });
package/plugin/index.ts CHANGED
@@ -220,6 +220,8 @@ async function stlStarlightAstroIntegration(
220
220
  HIGHLIGHT_THEMES: pluginConfig.highlighting.themes,
221
221
  CONTENT_PANEL_LAYOUT: pluginConfig.contentPanel.layout,
222
222
  EXPERIMENTAL_COLLAPSIBLE_SNIPPETS: pluginConfig.experimentalCollapsibleSnippets,
223
+ EXPERIMENTAL_COLLAPSIBLE_METHOD_DESCRIPTIONS:
224
+ pluginConfig.experimentalCollapsibleMethodDescriptions,
223
225
  PROPERTY_SETTINGS: pluginConfig.propertySettings,
224
226
  ENABLE_CONTEXT_MENU: pluginConfig.contextMenu,
225
227
  } satisfies Omit<typeof StlStarlightVirtualModule, 'MIDDLEWARE'>),
@@ -116,6 +116,14 @@ export type StainlessStarlightUserConfig = {
116
116
  */
117
117
  experimentalCollapsibleSnippets?: boolean;
118
118
 
119
+ /**
120
+ * Enable experimental collapsible method descriptions. Method descriptions will be
121
+ * collapsed if their content exceeds a certain length.
122
+ *
123
+ * @default false
124
+ */
125
+ experimentalCollapsibleMethodDescriptions?: boolean;
126
+
119
127
  /**
120
128
  * Whether to show the context menu with options like "Copy as Markdown" and "Open in ChatGPT".
121
129
  *
@@ -247,6 +255,7 @@ function normalizeConfig(partial: SomeStainlessStarlightUserConfig, command: Ast
247
255
  layout: partial.contentPanel?.layout ?? 'double-pane',
248
256
  },
249
257
  experimentalCollapsibleSnippets: partial.experimentalCollapsibleSnippets ?? false,
258
+ experimentalCollapsibleMethodDescriptions: partial.experimentalCollapsibleMethodDescriptions ?? false,
250
259
  propertySettings: {
251
260
  types: partial.propertySettings?.types ?? 'rich',
252
261
  collapseDescription: partial.propertySettings?.collapseDescription ?? true,
@@ -1,20 +1,23 @@
1
1
  import * as React from 'react';
2
- import { marked } from 'marked';
3
- import hljs from 'highlight.js';
4
- import { createMarkdownProcessor, type MarkdownProcessor } from '@astrojs/markdown-remark';
2
+ import { marked, Tokens } from 'marked';
3
+
4
+ import {
5
+ createMarkdownProcessor,
6
+ CreateShikiHighlighterOptions,
7
+ type MarkdownProcessor,
8
+ } from '@astrojs/markdown-remark';
5
9
  import remarkGfmAlerts from 'remark-github-alerts';
6
10
 
7
11
  import type { MarkdownHeading } from 'astro';
8
12
  import type { StarlightRouteData } from '@astrojs/starlight/route-data';
9
13
  import type * as SDKJSON from '@stainless/sdk-json';
10
- import { LanguageNames, type DocsLanguage } from '@stainless-api/docs-ui/routing';
14
+ import { LanguageNames, SupportedLanguageSyntaxes, type DocsLanguage } from '@stainless-api/docs-ui/routing';
11
15
 
12
16
  import {
13
17
  generateRouteList,
14
18
  generateRoute,
15
19
  parseStainlessPath,
16
20
  walkTree,
17
- SupportedLanguageSyntaxes,
18
21
  getLanguageSnippet,
19
22
  } from '@stainless-api/docs-ui/routing';
20
23
 
@@ -48,14 +51,16 @@ import {
48
51
  BREADCRUMB_CONFIG,
49
52
  PROPERTY_SETTINGS,
50
53
  ENABLE_CONTEXT_MENU,
54
+ EXPERIMENTAL_COLLAPSIBLE_METHOD_DESCRIPTIONS,
51
55
  } from 'virtual:stl-starlight-virtual-module';
52
56
  import style from '@stainless-api/docs-ui/style';
53
- import { createHighlighter, type BundledLanguage, type BundledTheme, type HighlighterGeneric } from 'shiki';
57
+ import { BundledTheme, createHighlighter, HighlighterGeneric, type BundledLanguage } from 'shiki';
54
58
  import { SnippetCode, SnippetContainer, SnippetRequestContainer } from '../components/SnippetCode';
55
59
  import type { StlStarlightMiddleware } from '../middlewareBuilder/stainlessMiddleware';
56
60
  import { ComponentProvider } from '@stainless-api/docs-ui/contexts/component';
57
61
  import { AIDropdown } from '../../stl-docs/components/AIDropdown';
58
62
  import { ChevronsUpDownIcon } from 'lucide-react';
63
+ import { MethodDescription } from '../components/MethodDescription';
59
64
 
60
65
  export function generateDocsRoutes(spec: SDKJSON.Spec) {
61
66
  const paths = generateRouteList({
@@ -151,19 +156,58 @@ export function buildPageNavigation(resource: SDKJSON.Resource, depth: number =
151
156
  return [...output, ...subs];
152
157
  }
153
158
 
154
- function renderMarkdown(content: string) {
155
- return marked.parse(content, { gfm: true }) as string;
159
+ async function renderMarkdown(content: string) {
160
+ const highlighter = await astroHighlight();
161
+
162
+ const renderer = {
163
+ code({ text, lang }: Tokens.Code) {
164
+ return shikiHighlight({
165
+ highlighter,
166
+ content: text,
167
+ language: lang,
168
+ });
169
+ },
170
+ };
171
+
172
+ marked.use({ renderer });
173
+ return marked.parse(content, {
174
+ gfm: true,
175
+ }) as string;
156
176
  }
157
177
 
158
- async function highlight(content: string, language?: string) {
159
- if (language === 'json') return hljs.highlight(content, { language }).value;
160
- const highlighter = await astroHighlight();
178
+ function shikiHighlight({
179
+ highlighter,
180
+ content,
181
+ language,
182
+ themes,
183
+ }: {
184
+ highlighter: HighlighterGeneric<BundledLanguage, BundledTheme>;
185
+ content: string;
186
+ language?: string;
187
+ themes?: CreateShikiHighlighterOptions['themes'] | Record<string, 'stainless-docs-json'>;
188
+ }) {
189
+ let _themes = themes;
190
+ if (!themes && language === 'json') {
191
+ _themes = {
192
+ light: 'stainless-docs-json',
193
+ dark: 'stainless-docs-json',
194
+ };
195
+ }
196
+
197
+ if (!_themes) {
198
+ _themes = HIGHLIGHT_THEMES;
199
+ }
161
200
  return highlighter.codeToHtml(content, {
162
201
  lang: language ?? 'javascript',
163
- themes: HIGHLIGHT_THEMES || {},
202
+ themes: _themes || {},
164
203
  });
165
204
  }
166
205
 
206
+ async function highlight(content: string, language?: string) {
207
+ const highlighter = await astroHighlight();
208
+ return shikiHighlight({ highlighter, content, language });
209
+ }
210
+
167
211
  export function SDKSelectReactComponent({
168
212
  selected,
169
213
  languages,
@@ -327,6 +371,7 @@ export function RenderSpec({
327
371
  SnippetCode,
328
372
  SnippetContainer,
329
373
  SnippetRequestContainer,
374
+ ...(EXPERIMENTAL_COLLAPSIBLE_METHOD_DESCRIPTIONS ? { MethodDescription } : {}),
330
375
  }}
331
376
  >
332
377
  <NavigationProvider basePath={BASE_PATH} selectedPath={path}>
@@ -392,6 +437,66 @@ export async function getReadmeContent(spec: SDKJSON.Spec, language: DocsLanguag
392
437
  return spec.readme[language];
393
438
  }
394
439
 
440
+ let astroShikiHighlighter:
441
+ | HighlighterGeneric<BundledLanguage, BundledTheme>
442
+ | Promise<HighlighterGeneric<BundledLanguage, BundledTheme>>
443
+ | null = null;
444
+
445
+ async function astroHighlight() {
446
+ if (astroShikiHighlighter) {
447
+ return astroShikiHighlighter;
448
+ }
449
+
450
+ astroShikiHighlighter = createHighlighter({
451
+ themes: [
452
+ 'github-light',
453
+ 'github-dark',
454
+ {
455
+ name: 'stainless-docs-json',
456
+ colors: {
457
+ 'editor.background': 'var(--stl-ui-background)',
458
+ 'editor.foreground': 'var(--stl-ui-foreground)',
459
+ },
460
+
461
+ tokenColors: [
462
+ {
463
+ scope: ['comment', 'punctuation.definition.comment'],
464
+ settings: { foreground: 'var(--stl-ui-foreground-muted)' },
465
+ },
466
+ // numbers, booleans, null
467
+ {
468
+ scope: ['constant.numeric', 'constant.language'],
469
+ settings: { foreground: 'var(--stl-ui-swatch-orange-base)' },
470
+ },
471
+ // strings
472
+ {
473
+ scope: ['string', 'string.quoted', 'string.template'],
474
+ settings: { foreground: 'var(--stl-ui-swatch-green-base)' },
475
+ },
476
+ // Keys, brackets
477
+ {
478
+ scope: ['support.type', 'meta'],
479
+ settings: { foreground: 'var(--stl-ui-foreground)' },
480
+ },
481
+ // brackets
482
+ {
483
+ scope: ['meta'],
484
+ settings: { foreground: 'var(--stl-ui-foreground-muted)' },
485
+ },
486
+ // built-in types
487
+ {
488
+ scope: ['support.type.builtin'],
489
+ settings: { foreground: 'var(--stl-ui-swatch-purple-base)' },
490
+ },
491
+ ],
492
+ },
493
+ ],
494
+ langs: SupportedLanguageSyntaxes,
495
+ });
496
+
497
+ return astroShikiHighlighter;
498
+ }
499
+
395
500
  // Astro's markdown processor is a singleton
396
501
  // Need to cache it instead of instanting per request
397
502
  let astroMarkdownProcessor: MarkdownProcessor;
@@ -409,18 +514,6 @@ async function astroMarkdown() {
409
514
  return astroMarkdownProcessor;
410
515
  }
411
516
 
412
- let astroShikiHighlighter: HighlighterGeneric<BundledLanguage, BundledTheme>;
413
- async function astroHighlight() {
414
- if (!astroShikiHighlighter) {
415
- astroShikiHighlighter = await createHighlighter({
416
- themes: ['github-light', 'github-dark'],
417
- langs: SupportedLanguageSyntaxes,
418
- });
419
- }
420
-
421
- return astroShikiHighlighter;
422
- }
423
-
424
517
  export async function astroMarkdownRender(content: string) {
425
518
  const md = await astroMarkdown();
426
519
  const output = await md.render(content);
@@ -143,3 +143,4 @@ if (readme) {
143
143
  <script src="../globalJs/copy.ts"></script>
144
144
  <script src="../globalJs/tooltip.ts"></script>
145
145
  <script src="../globalJs/code-snippets.ts"></script>
146
+ <script src="../globalJs/method-descriptions.ts"></script>
@@ -0,0 +1,36 @@
1
+ .stldocs-root {
2
+ .stl-method-description-overflow-wrapper {
3
+ display: flex;
4
+ align-items: center;
5
+ justify-content: center;
6
+ /* Minimum height to minimize layout shift */
7
+ min-height: 24px;
8
+ }
9
+ .stl-method-description {
10
+ .stldocs-method-description {
11
+ overflow: hidden;
12
+ max-height: 170px;
13
+ transition: max-height 0.3s ease;
14
+ position: relative;
15
+
16
+ &::after {
17
+ content: '';
18
+ position: absolute;
19
+ left: 0;
20
+ right: 0;
21
+ bottom: 0;
22
+ height: 3rem;
23
+ background: linear-gradient(to bottom, transparent, var(--stldocs-color-bg));
24
+ pointer-events: none;
25
+ }
26
+
27
+ &[data-collapsed='false'] {
28
+ max-height: none;
29
+ }
30
+
31
+ &[data-collapsed='false']::after {
32
+ display: none;
33
+ }
34
+ }
35
+ }
36
+ }
package/theme.css CHANGED
@@ -11,13 +11,13 @@
11
11
  @import './styles/overrides.css';
12
12
  @import './styles/code.css';
13
13
  @import './styles/sdk_select.css';
14
+ @import './styles/method-descriptions.css';
14
15
  @import '@stainless-api/ui-primitives/styles.css';
15
16
  @import './styles/mintlify-compat.css';
16
17
 
17
18
  @import '@stainless-api/docs-ui/styles/resets.css';
18
19
  @import '@stainless-api/docs-ui/styles/primitives.css';
19
20
  @import '@stainless-api/docs-ui/styles/main.css';
20
- @import '@stainless-api/docs-ui/styles/snippets.css';
21
21
  @import '@stainless-api/docs-ui/styles/search.css';
22
22
 
23
23
  @import './components/variables.css';
@@ -14,6 +14,7 @@ declare module 'virtual:stl-starlight-virtual-module' {
14
14
  export const HIGHLIGHT_THEMES: CreateShikiHighlighterOptions['themes'];
15
15
  export const CONTENT_PANEL_LAYOUT: 'double-pane' | 'single-pane';
16
16
  export const EXPERIMENTAL_COLLAPSIBLE_SNIPPETS: boolean | undefined;
17
+ export const EXPERIMENTAL_COLLAPSIBLE_METHOD_DESCRIPTIONS: boolean | undefined;
17
18
  export const PROPERTY_SETTINGS: PropertySettingsType;
18
19
  export const MIDDLEWARE: StlStarlightMiddleware;
19
20
  export const ENABLE_CONTEXT_MENU: boolean;