@writedocs/generator 0.1.0

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 (95) hide show
  1. package/LICENSE +15 -0
  2. package/README.md +17 -0
  3. package/astro.config.mjs +419 -0
  4. package/bin/writedocs.js +73 -0
  5. package/package.json +79 -0
  6. package/src/assets/wd_watermark.png +0 -0
  7. package/src/assets/wd_watermark_dark.png +0 -0
  8. package/src/cli/build-auth.js +53 -0
  9. package/src/cli/build.js +40 -0
  10. package/src/cli/dev.js +12 -0
  11. package/src/cli/generate-api-pages.js +359 -0
  12. package/src/cli/init.js +81 -0
  13. package/src/cli/preflight.js +40 -0
  14. package/src/cli/run-astro.js +57 -0
  15. package/src/cli/run-pagefind.js +66 -0
  16. package/src/cli/write-redirects-file.js +80 -0
  17. package/src/components/Accordion.astro +164 -0
  18. package/src/components/AccordionGroup.astro +40 -0
  19. package/src/components/ApiLangSelect.astro +168 -0
  20. package/src/components/ApiPlayground.astro +281 -0
  21. package/src/components/ApiReferencePanel.astro +1754 -0
  22. package/src/components/ApiSchemaField.astro +54 -0
  23. package/src/components/AppIcon.astro +32 -0
  24. package/src/components/Badge.astro +128 -0
  25. package/src/components/Callout.astro +168 -0
  26. package/src/components/Card.astro +136 -0
  27. package/src/components/CardGroup.astro +20 -0
  28. package/src/components/CodeGroup.astro +184 -0
  29. package/src/components/CopyPageMenu.astro +246 -0
  30. package/src/components/Danger.astro +12 -0
  31. package/src/components/Expandable.astro +126 -0
  32. package/src/components/Frame.astro +102 -0
  33. package/src/components/Hint.astro +99 -0
  34. package/src/components/Icon.astro +70 -0
  35. package/src/components/Image.astro +147 -0
  36. package/src/components/Info.astro +12 -0
  37. package/src/components/Note.astro +12 -0
  38. package/src/components/Parameter.astro +119 -0
  39. package/src/components/RequestExample.astro +33 -0
  40. package/src/components/ResponseExample.astro +19 -0
  41. package/src/components/Searchbar.astro +117 -0
  42. package/src/components/Step.astro +10 -0
  43. package/src/components/Steps.astro +32 -0
  44. package/src/components/Tab.astro +9 -0
  45. package/src/components/Tabs.astro +52 -0
  46. package/src/components/Tip.astro +12 -0
  47. package/src/components/Video.astro +135 -0
  48. package/src/components/Warning.astro +12 -0
  49. package/src/components/index.ts +48 -0
  50. package/src/content.config.ts +223 -0
  51. package/src/layout/BaseLayout.astro +750 -0
  52. package/src/layout/components/AnalyticsScripts.astro +77 -0
  53. package/src/layout/components/AskAiWidget.astro +37 -0
  54. package/src/layout/components/Breadcrumbs.astro +97 -0
  55. package/src/layout/components/ImageZoom.astro +19 -0
  56. package/src/layout/components/MobileMenu.astro +200 -0
  57. package/src/layout/components/NavTree.astro +351 -0
  58. package/src/layout/components/SearchModal.astro +42 -0
  59. package/src/layout/components/Sidebar.astro +122 -0
  60. package/src/layout/components/SiteFooter.astro +85 -0
  61. package/src/layout/components/TableOfContents.astro +117 -0
  62. package/src/layout/components/TopBar.astro +311 -0
  63. package/src/layout/styles/banner.css +44 -0
  64. package/src/layout/styles/base.css +234 -0
  65. package/src/layout/styles/dropdown.css +133 -0
  66. package/src/layout/styles/footer.css +108 -0
  67. package/src/layout/styles/image-zoom.css +50 -0
  68. package/src/layout/styles/mobile-menu.css +258 -0
  69. package/src/layout/styles/search-modal.css +122 -0
  70. package/src/layout/styles/topbar.css +437 -0
  71. package/src/lib/config.ts +2131 -0
  72. package/src/lib/mdx-auto-hydrate.js +70 -0
  73. package/src/lib/mdx-inject-builtins.js +87 -0
  74. package/src/lib/mdx-substitute-variables.js +66 -0
  75. package/src/lib/mdx-title-anchor-ids.js +84 -0
  76. package/src/lib/mermaid-rehype.js +72 -0
  77. package/src/lib/openapi-render.ts +479 -0
  78. package/src/lib/shiki-code-block.js +102 -0
  79. package/src/lib/shiki-copy-button.js +45 -0
  80. package/src/lib/styles-asset-integration.js +210 -0
  81. package/src/lib/writedocs-temp-dir.js +93 -0
  82. package/src/pages/404.astro +62 -0
  83. package/src/pages/[...slug].astro +1270 -0
  84. package/src/pages/[...slug].md.ts +78 -0
  85. package/src/pages/llms-full.txt.ts +71 -0
  86. package/src/pages/llms.txt.ts +141 -0
  87. package/src/scripts/banner.ts +20 -0
  88. package/src/scripts/dropdowns.ts +61 -0
  89. package/src/scripts/image-zoom.ts +66 -0
  90. package/src/scripts/mobile-menu.ts +55 -0
  91. package/src/scripts/search.ts +155 -0
  92. package/src/scripts/sidebar-scroll.ts +65 -0
  93. package/src/scripts/theme-toggle.ts +35 -0
  94. package/src/scripts/topbar-offset.ts +141 -0
  95. package/src/styles/global.css +18 -0
@@ -0,0 +1,184 @@
1
+ ---
2
+ // `dropdown` is only ever passed down from RequestExample/
3
+ // ResponseExample.astro today (CodeGroup itself is never used with it
4
+ // directly in any fixture/doc) - kept as a real prop rather than a
5
+ // RequestExample-only concern so the switcher-style logic lives in one
6
+ // place instead of being duplicated for a second "tabbed code" component.
7
+ interface Props {
8
+ dropdown?: boolean;
9
+ }
10
+ const { dropdown = false } = Astro.props as Props;
11
+ ---
12
+ <div class="wd-codegroup" data-dropdown={dropdown ? "true" : undefined}>
13
+ <slot />
14
+ </div>
15
+ <script>
16
+ // Mirrors Tabs.astro's own initTabs() almost exactly (see that file's
17
+ // comment for the dataset.wdInit re-run guard and astro:page-load
18
+ // rationale) - CodeGroup just reads each tab's label off the fenced
19
+ // block itself instead of a `title` prop, since a code fence's own
20
+ // ```lang title="..." meta (see shiki-code-block.js's parseMeta())
21
+ // already carries exactly that string, rendered as a .wd-code-title
22
+ // bar at build time - no reason to make an MDX author repeat it as a
23
+ // second prop on <CodeGroup>'s own children.
24
+ function labelFor(block: HTMLElement, index: number): string {
25
+ const title = block.querySelector<HTMLElement>('.wd-code-title')?.textContent?.trim();
26
+ if (title) return title;
27
+ // No `title=` meta on this fence - fall back to the language Shiki
28
+ // already tagged the <pre> with (data-language, always present),
29
+ // then a plain ordinal as the last resort for a truly bare ``` fence.
30
+ const lang = block.querySelector('pre[data-language]')?.getAttribute('data-language');
31
+ return lang ? lang.toUpperCase() : `Snippet ${index + 1}`;
32
+ }
33
+
34
+ function initCodeGroups(root: ParentNode) {
35
+ root.querySelectorAll<HTMLElement>('.wd-codegroup').forEach((group) => {
36
+ if (group.dataset.wdInit) return;
37
+ const blocks = Array.from(group.querySelectorAll<HTMLElement>(':scope > .wd-code-block'));
38
+ // A single-block <CodeGroup> has nothing to switch between - a
39
+ // one-tab tab strip (or a one-option dropdown) is pure noise, so
40
+ // it's left exactly as the CSS below already renders it: one
41
+ // plain bordered block, no nav.
42
+ if (blocks.length < 2) return;
43
+ group.dataset.wdInit = 'true';
44
+ // Only added once there's an actual tab strip/dropdown - see the
45
+ // CSS below, this is what hides each block's own now-redundant
46
+ // .wd-code-title bar in favor of the tab/option label showing the
47
+ // same text. Without JS (or a group too small to switch), that
48
+ // class never gets added and every block's title bar stays
49
+ // visible - the CodeGroup then just degrades to last commit's
50
+ // "stacked, shared border" look rather than losing the title text
51
+ // outright.
52
+ group.classList.add('wd-codegroup-tabbed');
53
+ blocks.forEach((block, i) => {
54
+ block.style.display = i === 0 ? '' : 'none';
55
+ });
56
+
57
+ if (group.dataset.dropdown === 'true') {
58
+ // RequestExample/ResponseExample's own `dropdown` prop - a
59
+ // <select> instead of a tab strip, per Mintlify's own prop of
60
+ // the same name/meaning on those two components. Same
61
+ // show/hide-blocks logic as the tab case below, just driven by
62
+ // a `change` event instead of individual button clicks.
63
+ const select = document.createElement('select');
64
+ select.className = 'wd-codegroup-select';
65
+ blocks.forEach((block, i) => {
66
+ const option = document.createElement('option');
67
+ option.value = String(i);
68
+ option.textContent = labelFor(block, i);
69
+ select.appendChild(option);
70
+ });
71
+ select.addEventListener('change', () => {
72
+ const index = Number(select.value);
73
+ blocks.forEach((b, i) => (b.style.display = i === index ? '' : 'none'));
74
+ });
75
+ group.prepend(select);
76
+ return;
77
+ }
78
+
79
+ const nav = document.createElement('div');
80
+ nav.className = 'wd-codegroup-nav';
81
+ blocks.forEach((block, i) => {
82
+ const btn = document.createElement('button');
83
+ btn.type = 'button';
84
+ btn.textContent = labelFor(block, i);
85
+ btn.className = 'wd-codegroup-tab' + (i === 0 ? ' active' : '');
86
+ btn.addEventListener('click', () => {
87
+ nav.querySelectorAll('.wd-codegroup-tab').forEach((b) => b.classList.remove('active'));
88
+ blocks.forEach((b) => (b.style.display = 'none'));
89
+ btn.classList.add('active');
90
+ block.style.display = '';
91
+ });
92
+ nav.appendChild(btn);
93
+ });
94
+ group.prepend(nav);
95
+ });
96
+ }
97
+ initCodeGroups(document);
98
+ document.addEventListener('astro:page-load', () => initCodeGroups(document));
99
+ </script>
100
+ <style is:global>
101
+ /* The single rounded border the docs promise ("a tabbed interface,
102
+ switching between snippets the same way Tabs does" - see
103
+ site/docs/content/components.mdx and kitchen-sink's own copy)
104
+ lives here now, on the group itself, not on each block -
105
+ overflow: hidden is load-bearing: every .wd-code-block inside loses
106
+ its own border-radius below, so it's this container's own radius
107
+ clipping the first/last block's corners (and, once tabbed, the
108
+ tab strip's own top corners) into that outer radius, not each
109
+ piece rounding itself. */
110
+ .wd-codegroup {
111
+ margin: 1.25rem 0;
112
+ border: 1px solid var(--wd-border);
113
+ border-radius: 0.55rem;
114
+ overflow: hidden;
115
+ }
116
+ /* Each fenced code block inside a <CodeGroup> is wrapped in a
117
+ .wd-code-block div by codeBlockTransformer (see astro.config.mjs /
118
+ src/lib/shiki-code-block.js) rather than being a bare <pre> - and
119
+ .wd-code-block itself now owns the card chrome (border/radius),
120
+ not the <pre> inside it (see [...slug].astro's base rule). Zeroes
121
+ out that per-block chrome entirely - margin (so blocks stack flush
122
+ with no visible gap between them, unlike a standalone block),
123
+ border and radius (both now the group's own job, above). */
124
+ .wd-codegroup > .wd-code-block {
125
+ margin: 0;
126
+ border: none;
127
+ border-radius: 0;
128
+ }
129
+ /* No-JS / too-small-to-tab fallback only - once initCodeGroups() above
130
+ hides every block but the active one, there's never two adjacent
131
+ *visible* blocks left for this to draw a line between, so it's
132
+ inert in the normal tabbed case. Without JS, every block stays
133
+ visible and stacked, and this is what still separates them. */
134
+ .wd-codegroup > .wd-code-block + .wd-code-block {
135
+ border-top: 1px solid var(--wd-border);
136
+ }
137
+ .wd-codegroup-nav {
138
+ display: flex;
139
+ gap: 0.25rem;
140
+ padding: 0 0.5rem;
141
+ overflow-x: auto;
142
+ background: var(--wd-surface);
143
+ border-bottom: 1px solid var(--wd-border);
144
+ }
145
+ .wd-codegroup-tab {
146
+ flex-shrink: 0;
147
+ padding: 0.55rem 0.7rem;
148
+ border: none;
149
+ border-bottom: 2px solid transparent;
150
+ background: none;
151
+ font-family: monospace;
152
+ font-size: 0.78rem;
153
+ font-weight: 500;
154
+ color: var(--wd-text-muted);
155
+ white-space: nowrap;
156
+ cursor: pointer;
157
+ }
158
+ .wd-codegroup-tab.active {
159
+ color: var(--wd-primary);
160
+ border-bottom-color: var(--wd-primary);
161
+ }
162
+ /* dropdown="true" case - same background/border-bottom as the tab
163
+ strip it replaces, so a RequestExample/ResponseExample's dropdown
164
+ mode still reads as the same "code block chrome" family. */
165
+ .wd-codegroup-select {
166
+ display: block;
167
+ width: 100%;
168
+ padding: 0.5rem 0.7rem;
169
+ border: none;
170
+ border-bottom: 1px solid var(--wd-border);
171
+ background: var(--wd-surface);
172
+ color: var(--wd-text);
173
+ font-family: monospace;
174
+ font-size: 0.78rem;
175
+ font-weight: 500;
176
+ cursor: pointer;
177
+ }
178
+ /* The tab/option label already shows this same text - see labelFor()
179
+ above - so the per-block title bar underneath would just repeat
180
+ it. */
181
+ .wd-codegroup-tabbed .wd-code-title {
182
+ display: none;
183
+ }
184
+ </style>
@@ -0,0 +1,246 @@
1
+ ---
2
+ // The "Copy page" split button - opt-in via writedocs.json's `contextMenu`
3
+ // field (see contextMenuSchema in lib/config.ts). A primary button
4
+ // (always-visible "Copy page" action, copies this page's Markdown
5
+ // straight to the clipboard on click - see initCopyPageMenu() in
6
+ // [...slug].astro) plus a small caret-only button next to it that opens
7
+ // a dropdown for the rest (View as Markdown, Open in ChatGPT/Claude/
8
+ // Perplexity) - only the caret button is a .wd-dropdown-trigger, so only
9
+ // clicking (or hovering, per the shared CSS) *it* opens the menu, not
10
+ // the primary action next to it. The dropdown itself reuses the exact
11
+ // .wd-dropdown/.wd-dropdown-trigger/.wd-dropdown-menu/
12
+ // .wd-dropdown-menu-panel markup pattern BaseLayout.astro's topbar
13
+ // selectors already use - open/close/outside-click/Escape all come free
14
+ // from BaseLayout's initDropdowns(document), which queries `.wd-dropdown`
15
+ // anywhere in the page, not just its own template. See BaseLayout.astro's
16
+ // own comment on the :global() wrapping its copy of that CSS needed so
17
+ // the rules reach markup rendered by a different component (this one).
18
+ //
19
+ // Rendered from [...slug].astro (see its own comment on where/when),
20
+ // which passes in everything needed to build both same-origin links (the
21
+ // .md route from [...slug].md.ts) and the external "Open in X" links
22
+ // (which need an absolute URL - siteUrl is null on sites with no
23
+ // writedocs.json `domain` set, in which case those three are left out
24
+ // entirely rather than linking a service at a URL it could never fetch).
25
+ import { Icon } from 'astro-icon/components';
26
+ import type { ContextMenuConfig } from '../lib/config';
27
+
28
+ interface Props {
29
+ currentPath: string; // e.g. "/" or "/guides/foo/" - see hrefForSlug() in [...slug].astro
30
+ siteUrl: string | null;
31
+ contextMenu: ContextMenuConfig;
32
+ }
33
+
34
+ const { currentPath, siteUrl, contextMenu } = Astro.props as Props;
35
+
36
+ // The [...slug].md.ts route this page is also served at - same
37
+ // normalizeEntryId()-driven convention that file uses to build its own
38
+ // getStaticPaths params ("/" -> "index.md", everything else -> its own
39
+ // slug with a literal ".md" appended instead of the trailing slash).
40
+ const mdPath = currentPath === '/' ? '/index.md' : `${currentPath.replace(/\/$/, '')}.md`;
41
+ const mdAbsoluteUrl = siteUrl ? `${siteUrl}${mdPath}` : null;
42
+
43
+ // Well-known query-string conventions for starting a new conversation
44
+ // pre-seeded with a prompt (not an official API for any of the three -
45
+ // just the same `?q=`/`/new?q=`/`/search?q=` pattern each already
46
+ // supports for search-engine-style deep links). Pointing the prompt at
47
+ // this page's own .md URL rather than pasting the page content directly
48
+ // into the query string keeps the link itself short and lets the
49
+ // assistant fetch the current content instead of a snapshot baked into
50
+ // a bookmarked/shared link.
51
+ const promptFor = (url: string) => `Read ${url} so you can answer questions about it.`;
52
+
53
+ const assistants = [
54
+ { id: 'chatgpt' as const, label: 'ChatGPT', icon: 'simple-icons:openai', buildHref: (q: string) => `https://chatgpt.com/?q=${q}` },
55
+ { id: 'claude' as const, label: 'Claude', icon: 'simple-icons:claude', buildHref: (q: string) => `https://claude.ai/new?q=${q}` },
56
+ { id: 'perplexity' as const, label: 'Perplexity', icon: 'simple-icons:perplexity', buildHref: (q: string) => `https://www.perplexity.ai/search?q=${q}` },
57
+ ];
58
+
59
+ // No siteUrl at all -> mdAbsoluteUrl is null -> none of these render,
60
+ // regardless of what contextMenu.openIn lists: a link these services
61
+ // could never actually fetch (a bare relative path, meaningless once
62
+ // it's opened on a different origin) is worse than not offering it.
63
+ const enabledAssistants = mdAbsoluteUrl
64
+ ? assistants.filter((a) => contextMenu.openIn.includes(a.id))
65
+ : [];
66
+ ---
67
+ <div class="wd-copy-page-split">
68
+ <button type="button" class="wd-copy-page-primary" data-md-path={mdPath} aria-label="Copy page as Markdown">
69
+ <Icon name="lucide:copy" class="wd-copy-page-icon" />
70
+ <span class="wd-copy-page-primary-label">Copy page</span>
71
+ </button>
72
+ <div class="wd-dropdown wd-copy-page">
73
+ <button type="button" class="wd-dropdown-trigger wd-copy-page-caret-trigger" aria-haspopup="true" aria-expanded="false" aria-label="More copy options">
74
+ <svg class="wd-dropdown-caret" width="10" height="10" viewBox="0 0 10 10" aria-hidden="true">
75
+ <path d="M2 3.5L5 6.5L8 3.5" stroke="currentColor" stroke-width="1.4" fill="none" stroke-linecap="round" stroke-linejoin="round" />
76
+ </svg>
77
+ </button>
78
+ <div class="wd-dropdown-menu wd-copy-page-menu" role="menu">
79
+ <div class="wd-dropdown-menu-panel wd-copy-page-menu-panel">
80
+ <a class="wd-copy-page-item" href={mdPath} target="_blank" rel="noopener">
81
+ <Icon name="lucide:external-link" class="wd-copy-page-icon" />
82
+ <span class="wd-copy-page-item-text">
83
+ <span class="wd-copy-page-item-title">View as Markdown</span>
84
+ <span class="wd-copy-page-item-desc">View this page as plain text</span>
85
+ </span>
86
+ </a>
87
+ {enabledAssistants.map((a) => (
88
+ <a class="wd-copy-page-item" href={a.buildHref(encodeURIComponent(promptFor(mdAbsoluteUrl!)))} target="_blank" rel="noopener">
89
+ <Icon name={a.icon} class="wd-copy-page-icon" />
90
+ <span class="wd-copy-page-item-text">
91
+ <span class="wd-copy-page-item-title">Open in {a.label}</span>
92
+ <span class="wd-copy-page-item-desc">Ask questions about this page</span>
93
+ </span>
94
+ </a>
95
+ ))}
96
+ </div>
97
+ </div>
98
+ </div>
99
+ </div>
100
+
101
+ <style>
102
+ /* The split button: a primary "Copy page" action (always visible,
103
+ copies on click - see initCopyPageMenu() in [...slug].astro) and a
104
+ small caret-only button next to it that opens the rest (View as
105
+ Markdown, Open in X). Each button gets its own half of the shared
106
+ border-radius (left corners on the primary button, right corners on
107
+ the caret button) rather than `overflow: hidden` on the wrapper -
108
+ overflow: hidden was tried first and clips more than intended: the
109
+ dropdown menu itself is an absolutely-positioned descendant of
110
+ .wd-dropdown, which lives inside this wrapper, so clipping the
111
+ wrapper's overflow silently clipped the open menu away too (the
112
+ caret still visibly rotated on hover/open, since that's a transform
113
+ on the caret itself, not the menu - only the menu's own visibility
114
+ was affected, which is what made this look like "nothing happens"
115
+ rather than an obvious rendering glitch). */
116
+ .wd-copy-page-split {
117
+ display: flex;
118
+ align-items: stretch;
119
+ border: 1px solid var(--wd-border);
120
+ border-radius: 0.4rem;
121
+ flex-shrink: 0;
122
+ }
123
+ /* .wd-copy-page is this instance's own class on the .wd-dropdown div
124
+ wrapping the caret button + menu (see BaseLayout.astro's shared
125
+ .wd-dropdown rule for `position: relative`, which still applies
126
+ alongside this). .wd-copy-page-split's `align-items: stretch` above
127
+ stretches this div's own height to match the primary button next to
128
+ it, but that alone doesn't stretch the *caret button inside it* -
129
+ .wd-dropdown is a plain block div, not a flex container, so its
130
+ child sizes itself independently and just sits at the top of the
131
+ now-taller div, which is what made the arrow look vertically
132
+ off-center against the primary button. Making this div a flex
133
+ container too (with its own stretch) propagates the height down to
134
+ the button, which centers the svg within it via the shared
135
+ .wd-dropdown-trigger rule's own align-items: center. */
136
+ .wd-copy-page {
137
+ display: flex;
138
+ align-items: stretch;
139
+ }
140
+ .wd-copy-page-primary {
141
+ display: flex;
142
+ align-items: center;
143
+ gap: 0.35rem;
144
+ padding: 0.35rem 0.65rem;
145
+ border: none;
146
+ border-radius: 0.35rem 0 0 0.35rem;
147
+ background: none;
148
+ cursor: pointer;
149
+ color: var(--wd-text-muted);
150
+ font-size: 0.85rem;
151
+ font-weight: 500;
152
+ font-family: inherit;
153
+ }
154
+ .wd-copy-page-primary:hover {
155
+ color: var(--wd-text);
156
+ background: var(--wd-surface);
157
+ }
158
+ /* Toggled by initCopyPageMenu() for ~1.5s after a successful copy -
159
+ the label itself swaps to "Copied!" via JS (not CSS content, so a
160
+ screen reader announces it), this just tints icon+text green to
161
+ match. */
162
+ .wd-copy-page-primary.wd-copy-page-copied {
163
+ color: #16a34a;
164
+ }
165
+ /* Overrides the shared .wd-dropdown-trigger base rule (BaseLayout.astro)
166
+ down to an icon-only square button - no padding/border of its own
167
+ beyond the divider line, and only the right corners rounded (to
168
+ match .wd-copy-page-split's own outer radius on that side - see the
169
+ comment above on why the wrapper itself no longer clips to its
170
+ radius via overflow: hidden). */
171
+ .wd-copy-page-caret-trigger {
172
+ padding: 0.35rem 0.5rem;
173
+ border: none;
174
+ border-left: 1px solid var(--wd-border);
175
+ border-radius: 0 0.35rem 0.35rem 0;
176
+ }
177
+ .wd-copy-page-menu {
178
+ left: auto;
179
+ right: 0;
180
+ }
181
+ /* overflow: hidden here (in addition to .wd-copy-page-item below no
182
+ longer overflowing its own box) is defensive belt-and-suspenders -
183
+ keeps any future item's hover background from ever visibly poking
184
+ past the panel's own rounded corners, the exact bug .wd-copy-page-item
185
+ used to have. */
186
+ .wd-copy-page-menu-panel {
187
+ min-width: 260px;
188
+ overflow: hidden;
189
+ }
190
+ .wd-copy-page-icon {
191
+ width: 1em;
192
+ height: 1em;
193
+ flex-shrink: 0;
194
+ }
195
+ /* .wd-dropdown-menu a (BaseLayout.astro) is single-line only
196
+ (align-items: center on one row of text) - these items need a
197
+ title + description subtitle, so .wd-copy-page-item re-implements
198
+ that layout instead (icon top-aligned next to a two-line text
199
+ stack) rather than trying to make the shared single-line rule cover
200
+ both shapes.
201
+ No explicit `width` here - deliberately: this codebase doesn't set
202
+ a global `box-sizing: border-box` reset (see the comment on
203
+ Tailwind's preflight in src/styles/global.css), so a block-level
204
+ element's default `width: auto` already correctly fills its
205
+ container *net of* its own padding, while an explicit `width: 100%`
206
+ would add that padding on top instead, overflowing past the
207
+ container's edge - exactly what an earlier version of this rule did
208
+ (visible as the hover background bleeding out past the panel's
209
+ rounded corner on hover). Leave width unset rather than reaching for
210
+ `box-sizing: border-box` here alone, which would just be one rule
211
+ quietly relying on a codebase-wide convention it doesn't establish. */
212
+ .wd-copy-page-item {
213
+ display: flex;
214
+ align-items: flex-start;
215
+ gap: 0.6rem;
216
+ padding: 0.5rem 0.6rem;
217
+ border: none;
218
+ border-radius: 0.35rem;
219
+ background: none;
220
+ text-decoration: none;
221
+ color: var(--wd-text);
222
+ font-family: inherit;
223
+ text-align: left;
224
+ cursor: pointer;
225
+ }
226
+ .wd-copy-page-item:hover {
227
+ background: var(--wd-surface);
228
+ }
229
+ .wd-copy-page-item .wd-copy-page-icon {
230
+ margin-top: 0.15rem;
231
+ color: var(--wd-text-muted);
232
+ }
233
+ .wd-copy-page-item-text {
234
+ display: flex;
235
+ flex-direction: column;
236
+ gap: 0.1rem;
237
+ }
238
+ .wd-copy-page-item-title {
239
+ font-size: 0.86rem;
240
+ font-weight: 500;
241
+ }
242
+ .wd-copy-page-item-desc {
243
+ font-size: 0.76rem;
244
+ color: var(--wd-text-muted);
245
+ }
246
+ </style>
@@ -0,0 +1,12 @@
1
+ ---
2
+ // Shorthand for <Callout type="danger">. See Callout.astro's own comment.
3
+ import Callout from './Callout.astro';
4
+ interface Props {
5
+ title?: string;
6
+ // See Callout.astro's own comment - set automatically by
7
+ // remarkCalloutAnchorIds, not meant to be passed by hand.
8
+ _titleId?: string;
9
+ }
10
+ const { title, _titleId } = Astro.props as Props;
11
+ ---
12
+ <Callout type="danger" title={title} _titleId={_titleId}><slot /></Callout>
@@ -0,0 +1,126 @@
1
+ ---
2
+ // The collapsible "Show/Hide properties" wrapper that pairs with
3
+ // Parameter for nested object properties - modeled directly on
4
+ // Mintlify's own Expandable (https://www.mintlify.com/docs/components/
5
+ // expandables), the reference this was built against. Structurally
6
+ // near-identical to Accordion.astro (same <details>/<summary>, same
7
+ // custom chevron svg, same border/radius language) - deliberately reused
8
+ // rather than reinvented, since "apply the same styles as our api
9
+ // pages" means this should read as the same family of collapsible box
10
+ // as everything else in this site, not a one-off. Two real differences
11
+ // from Accordion, both because the two components solve different
12
+ // problems: this defaults to *open* (Accordion defaults closed - an
13
+ // Expandable's whole point in the reference design is showing a nested
14
+ // object's properties immediately, with room to collapse if they're
15
+ // not needed, the opposite default of an FAQ-style Accordion), and its
16
+ // toggle label is computed ("Show "/"Hide " + title) rather than a
17
+ // fixed heading, updated live via the <script> below rather than at
18
+ // build time - a bare CSS/HTML approach can't express "this text
19
+ // depends on live open/closed state" the way a details' own [open]
20
+ // attribute can drive a CSS transform.
21
+ interface Props {
22
+ title?: string;
23
+ defaultOpen?: boolean;
24
+ }
25
+ const { title = "properties", defaultOpen = true } = Astro.props as Props;
26
+ ---
27
+
28
+ <details class="wd-expandable" open={defaultOpen}>
29
+ <summary>
30
+ <svg class="wd-expandable-chevron" width="11" height="11" viewBox="0 0 10 10" aria-hidden="true">
31
+ <path
32
+ d="M2 3.5L5 6.5L8 3.5"
33
+ stroke="currentColor"
34
+ stroke-width="1.4"
35
+ fill="none"
36
+ stroke-linecap="round"
37
+ stroke-linejoin="round"></path>
38
+ </svg>
39
+ <span class="wd-expandable-label" data-title={title}>{defaultOpen ? `Hide ${title}` : `Show ${title}`}</span>
40
+ </summary>
41
+ <div class="wd-expandable-body"><slot /></div>
42
+ </details>
43
+ <script>
44
+ // The label text isn't a static title (unlike Accordion's own
45
+ // <summary>) - it flips between "Show "/"Hide " + title as the
46
+ // <details> element's own open/closed state changes, so it has to be
47
+ // driven by the native `toggle` event rather than expressed purely
48
+ // in markup/CSS. data-title (set from the `title` prop above) is
49
+ // what the prefix gets rebuilt around each time.
50
+ function initExpandables(root: ParentNode) {
51
+ root.querySelectorAll<HTMLDetailsElement>(".wd-expandable").forEach((details) => {
52
+ if (details.dataset.wdInit) return;
53
+ details.dataset.wdInit = "true";
54
+ const label = details.querySelector<HTMLElement>(".wd-expandable-label");
55
+ details.addEventListener("toggle", () => {
56
+ const title = label?.dataset.title ?? "properties";
57
+ if (label) label.textContent = `${details.open ? "Hide" : "Show"} ${title}`;
58
+ });
59
+ });
60
+ }
61
+ initExpandables(document);
62
+ document.addEventListener("astro:page-load", () => initExpandables(document));
63
+ </script>
64
+ <style>
65
+ .wd-expandable {
66
+ border: 1px solid var(--wd-border);
67
+ border-radius: 0.6rem;
68
+ margin: 0.75rem 0;
69
+ overflow: hidden;
70
+ }
71
+ .wd-expandable summary {
72
+ display: flex;
73
+ align-items: center;
74
+ gap: 0.6rem;
75
+ padding: 0.5rem 1rem;
76
+ cursor: pointer;
77
+ list-style: none;
78
+ }
79
+ /* Firefox/Chrome use ::marker for the default disclosure triangle,
80
+ Safari still needs the older, WebKit-specific pseudo-element -
81
+ both zeroed out so only the svg above ever shows. Same as
82
+ Accordion.astro's own identical rules. */
83
+ .wd-expandable summary::-webkit-details-marker {
84
+ display: none;
85
+ }
86
+ .wd-expandable summary::marker {
87
+ content: "";
88
+ }
89
+ .wd-expandable summary:hover {
90
+ background: var(--wd-surface);
91
+ }
92
+ /* The divider between the "Show/Hide" row and the fields beneath it -
93
+ only drawn while open, matching the reference design (a closed
94
+ Expandable has nothing below the row to separate from, so drawing
95
+ a border under it while collapsed would just be an orphaned line
96
+ under an otherwise plain pill). */
97
+ .wd-expandable[open] > summary {
98
+ border-bottom: 1px solid var(--wd-border);
99
+ }
100
+ .wd-expandable-chevron {
101
+ flex-shrink: 0;
102
+ color: var(--wd-text-muted);
103
+ transition: transform 0.15s ease;
104
+ }
105
+ .wd-expandable[open] > summary .wd-expandable-chevron {
106
+ transform: rotate(180deg);
107
+ }
108
+ /* Plain weight + muted color, not bold - unlike Accordion's own
109
+ title (a real heading for the section beneath it), this label is a
110
+ UI affordance ("there's more, click to see it"/"click to hide it
111
+ again"), not content in its own right, matching the lighter-weight
112
+ treatment the reference design uses for it. */
113
+ .wd-expandable-label {
114
+ color: var(--wd-text-muted);
115
+ font-size: 0.75rem;
116
+ }
117
+ /* No top padding on the body itself - Parameter's own top-level
118
+ padding (0.9rem 0 - see its own comment) is exactly the gap wanted
119
+ between the divider above and the first field's name/type row, so
120
+ adding more here would just double it up. Bottom/side padding
121
+ still applies normally since Parameter has no matching
122
+ horizontal inset of its own. */
123
+ .wd-expandable-body {
124
+ padding: 0 1rem 0.9rem;
125
+ }
126
+ </style>
@@ -0,0 +1,102 @@
1
+ ---
2
+ // A generic "put a rounded, bordered card around this" wrapper -
3
+ // deliberately different from Image (a specific <img> with its own
4
+ // src/srcDark/size handling): Frame wraps *arbitrary* slot content - a
5
+ // raw HTML <img>, a markdown ![]() image (which the markdown pipeline
6
+ // still turns into a real <img>, just possibly wrapped in a <p> by the
7
+ // surrounding paragraph parsing), a <video>, an <iframe> embed, or
8
+ // anything else a site author already has and just wants the card
9
+ // treatment on - matching Mintlify's own <Frame>, which exists for
10
+ // exactly this "I already have markup, give it the frame" case rather
11
+ // than duplicating Image's own prop-driven API. Nothing here about this
12
+ // component's name collides with frontmatter's own `mode: "frame"`
13
+ // page-rendering option (BaseLayout.astro) - that's an unrelated,
14
+ // page-level concept keyed off a plain string, not a JSX component.
15
+ interface Props {
16
+ caption?: string;
17
+ }
18
+ const { caption } = Astro.props as Props;
19
+ ---
20
+
21
+ <figure class="wd-frame">
22
+ <div class="wd-frame-content">
23
+ <slot />
24
+ </div>
25
+ {caption && <figcaption class="wd-frame-caption">{caption}</figcaption>}
26
+ </figure>
27
+ <style>
28
+ /* Border/background/padding live on the outer <figure>, not the inner
29
+ content div, so a caption renders *inside* the same bordered card as
30
+ the media above it - one continuous box, not a border tight around
31
+ just the image with plain caption text floating separately below it
32
+ (an earlier version of this file did exactly that; a real reference
33
+ screenshot showing the border wrapping the caption too is what this
34
+ was rebuilt against). Image.astro's own caption case mirrors this
35
+ same card language for the same reason - see its own comment. */
36
+ .wd-frame {
37
+ margin: 1.25rem 0;
38
+ border: 1px solid var(--wd-border);
39
+ border-radius: 0.75rem;
40
+ background: var(--wd-surface);
41
+ padding: 0.5rem;
42
+ /* Belt-and-suspenders alongside the border - see Image.astro's own
43
+ identical box-shadow comment. var(--wd-border)/var(--wd-surface)
44
+ are low-contrast by default, and a real user report traced an
45
+ "invisible card" complaint to exactly that: a demo asset that
46
+ happened to be the same image as the page's own background,
47
+ leaving nothing but a barely-visible 1px line to signal a card
48
+ was even there. A shadow reads regardless of border/background
49
+ contrast against whatever the page looks like. */
50
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
51
+ }
52
+ .wd-frame-content {
53
+ display: flex;
54
+ justify-content: center;
55
+ align-items: center;
56
+ }
57
+ /* Slot content isn't rendered by this component's own template - it's
58
+ whatever markup the author put between <Frame> and </Frame> (a raw
59
+ <img>, markdown's own ![]() output, possibly wrapped in a <p> by the
60
+ surrounding paragraph parsing) - same as Accordion-body's own
61
+ :global(p:first-child)/:global(p:last-child) rules, this needs
62
+ :global() to reach it at all; without it these selectors would
63
+ compile with this component's own scope attribute and never match
64
+ anything actually inside <slot />. iframe included alongside img/
65
+ video since an embed (a YouTube player, a CodeSandbox, ...) is just
66
+ as plausible a Frame child as an image - border: 0 clears the
67
+ default inset border some browsers still draw around a bare
68
+ <iframe>, which would otherwise double up with this card's own
69
+ border. border-radius on the media itself (rather than clipping via
70
+ the container's own overflow: hidden) matches Image's own approach -
71
+ modern browsers clip img/video/iframe content to their own
72
+ border-radius directly, no wrapping element needed purely for that. */
73
+ .wd-frame-content :global(img),
74
+ .wd-frame-content :global(video),
75
+ .wd-frame-content :global(iframe) {
76
+ display: block;
77
+ width: 100%;
78
+ max-width: 100%;
79
+ height: auto;
80
+ margin: 0;
81
+ border: 0;
82
+ border-radius: 0.5rem;
83
+ }
84
+ /* A markdown ![]() image lands wrapped in its own <p> (the surrounding
85
+ paragraph the image syntax was written inside) - display: contents
86
+ drops that wrapper's own box entirely, so the width: 100% rule above
87
+ still sizes the <img> itself against .wd-frame-content directly,
88
+ regardless of whether the author wrote a raw <img> (no wrapping <p>
89
+ at all) or markdown image syntax (always one). Harmless no-op for
90
+ any other block-level slot content (a code block, a Callout, ...) -
91
+ display: contents only changes how *this* one wrapper participates
92
+ in layout, never anything it contains. */
93
+ .wd-frame-content :global(p) {
94
+ display: contents;
95
+ }
96
+ .wd-frame-caption {
97
+ margin: 0.6rem 0 0;
98
+ text-align: center;
99
+ color: var(--wd-text-muted);
100
+ font-size: 0.75rem;
101
+ }
102
+ </style>