@stainless-api/docs 0.1.0-beta.32 → 0.1.0-beta.34

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,20 @@
1
1
  # @stainless-api/docs
2
2
 
3
+ ## 0.1.0-beta.34
4
+
5
+ ### Patch Changes
6
+
7
+ - f5173e9: fix: theme flickering
8
+
9
+ ## 0.1.0-beta.33
10
+
11
+ ### Patch Changes
12
+
13
+ - 7ef572c: feat: updated dropdowns
14
+ - Updated dependencies [7ef572c]
15
+ - @stainless-api/docs-ui@0.1.0-beta.27
16
+ - @stainless-api/ui-primitives@0.1.0-beta.20
17
+
3
18
  ## 0.1.0-beta.32
4
19
 
5
20
  ### Patch Changes
@@ -91,11 +91,6 @@
91
91
  --stldocs-title-padding-y: 2.5rem;
92
92
 
93
93
  --stldocs-z-index-theme-select: 1000;
94
- --stldocs-button-border-radius: var(--sl-button-border-radius);
95
- --stldocs-button-size: var(--sl-button-size);
96
- --stldocs-button-padding-x: var(--sl-button-padding-x);
97
- --stldocs-button-padding-y: var(--sl-button-padding-y);
98
-
99
94
  --sl-content-pad-x: 1rem;
100
95
  }
101
96
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stainless-api/docs",
3
- "version": "0.1.0-beta.32",
3
+ "version": "0.1.0-beta.34",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -14,7 +14,8 @@
14
14
  "./stainless-docs/mintlify-compat": "./stl-docs/components/mintlify-compat/index.ts",
15
15
  "./mintlify-compat.css": "./styles/mintlify-compat.css",
16
16
  "./font-imports": "./styles/fonts.css",
17
- "./components": "./stl-docs/components/index.ts"
17
+ "./components": "./stl-docs/components/index.ts",
18
+ "./components/scripts": "./stl-docs/components/scripts.ts"
18
19
  },
19
20
  "keywords": [],
20
21
  "author": "",
@@ -50,8 +51,8 @@
50
51
  "remark-gfm": "^4.0.1",
51
52
  "remark-stringify": "^11.0.0",
52
53
  "unified": "^11.0.5",
53
- "@stainless-api/docs-ui": "0.1.0-beta.26",
54
- "@stainless-api/ui-primitives": "0.1.0-beta.19"
54
+ "@stainless-api/docs-ui": "0.1.0-beta.27",
55
+ "@stainless-api/ui-primitives": "0.1.0-beta.20"
55
56
  },
56
57
  "devDependencies": {
57
58
  "@astrojs/check": "^0.9.5",
@@ -65,8 +66,8 @@
65
66
  "typescript": "5.9.3",
66
67
  "vite": "^6.3.6",
67
68
  "zod": "^4.0.0",
68
- "@stainless/sdk-json": "^0.1.0-beta.0",
69
- "@stainless/eslint-config": "0.1.0-beta.0"
69
+ "@stainless/eslint-config": "0.1.0-beta.0",
70
+ "@stainless/sdk-json": "^0.1.0-beta.0"
70
71
  },
71
72
  "scripts": {
72
73
  "vendor-deps": "pnpm tsx scripts/vendor_deps.ts",
@@ -138,15 +138,17 @@ const readmeSlug = language === 'http' ? BASE_PATH : `${BASE_PATH}/${language}`;
138
138
  <script>
139
139
  import { navigate } from 'astro:transitions/client';
140
140
  import { updateSelectedLanguage } from '../languages';
141
- import { initDropdown } from '@stainless-api/docs-ui/src/components/scripts/dropdown';
141
+ import { initDropdown } from '@stainless-api/docs/components/scripts';
142
142
  import { BASE_PATH } from 'virtual:stl-starlight-virtual-module';
143
143
  import { getPageLoadEvent } from '../helpers/getPageLoadEvent';
144
144
 
145
145
  document.addEventListener(getPageLoadEvent(), () => {
146
+ const sdkSelect = document.getElementById('sidebar-sdk-select');
147
+ if (!sdkSelect) return;
146
148
  initDropdown({
147
- dropdownId: 'sidebar-sdk-select',
149
+ root: sdkSelect,
148
150
  onSelect: (value) => {
149
- const originalLanguage = document.getElementById('sidebar-sdk-select')?.dataset.currentValue;
151
+ const originalLanguage = sdkSelect.dataset.currentValue;
150
152
  navigate(updateSelectedLanguage(BASE_PATH, originalLanguage, value));
151
153
  },
152
154
  });
@@ -2,22 +2,23 @@
2
2
  import { Icon } from '@astrojs/starlight/components';
3
3
  import { DocsSearch } from './SearchIsland';
4
4
  import { SEARCH } from 'virtual:stl-starlight-virtual-module';
5
+ import { Button } from '@stainless-api/ui-primitives';
5
6
  ---
6
7
 
7
8
  <site-search>
8
- <button
9
- popovertarget="stldocs-search"
9
+ <Button
10
+ popoverTarget="stldocs-search"
10
11
  data-open-modal
11
12
  aria-label={Astro.locals.t('search.label')}
12
13
  aria-keyshortcuts="Control+K"
13
- class="stldocs-button stldocs-button-secondary"
14
+ variant="outline"
14
15
  >
15
16
  <Icon name="magnifier" />
16
17
  <span class="sl-hidden md:sl-block" aria-hidden="true">{Astro.locals.t('search.label')}</span>
17
18
  <kbd class="sl-hidden md:sl-flex" style="display: none;">
18
19
  <kbd>{Astro.locals.t('search.ctrlKey')}</kbd><kbd>K</kbd>
19
20
  </kbd>
20
- </button>
21
+ </Button>
21
22
 
22
23
  <DocsSearch
23
24
  client:only="react"
@@ -209,14 +209,16 @@ export function wireAIDropdown() {
209
209
  // we hide the Cursor option on non-desktop devices
210
210
  for (const option of flatOptions) {
211
211
  if (option.clientHidden === true) {
212
- const el = document.querySelector(`[data-value="${option.id}"]`);
212
+ const el = document.querySelector(
213
+ `[data-dropdown-id="ai-dropdown-button"] [data-value="${option.id}"]`,
214
+ );
213
215
  if (el) {
214
216
  el.remove();
215
217
  }
216
218
  }
217
219
  }
218
220
 
219
- const dropdowns = document.querySelectorAll('[data-dropdown-id]');
221
+ const dropdowns = document.querySelectorAll('[data-dropdown-id="ai-dropdown-button"]');
220
222
 
221
223
  dropdowns.forEach((dropdown) => {
222
224
  initDropdownButton({
@@ -4,7 +4,7 @@ import { updateSelectedLanguage } from '../languages';
4
4
  import { navigate } from 'astro:transitions/client';
5
5
  import { getPageLoadEvent } from '../helpers/getPageLoadEvent.ts';
6
6
 
7
- import { initDropdown } from '@stainless-api/docs-ui/src/components/scripts/dropdown';
7
+ import { initDropdown } from '@stainless-api/docs/components/scripts';
8
8
 
9
9
  history.scrollRestoration = 'auto';
10
10
 
@@ -22,10 +22,13 @@ window.addEventListener('popstate', (ev: PopStateEvent) => {
22
22
  });
23
23
 
24
24
  document.addEventListener(getPageLoadEvent(), () => {
25
+ const rootElement = document.getElementById('stldocs-snippet-select');
26
+ if (!rootElement) return;
27
+
25
28
  initDropdown({
26
- dropdownId: 'stldocs-snippet-select',
29
+ root: rootElement,
27
30
  onSelect: (value) => {
28
- const originalLanguage = document.getElementById('stldocs-snippet-select')?.dataset.currentValue;
31
+ const originalLanguage = rootElement?.dataset.currentValue;
29
32
  navigate(updateSelectedLanguage(BASE_PATH, originalLanguage, value));
30
33
  },
31
34
  });
@@ -33,14 +33,13 @@ import {
33
33
  SDKResource,
34
34
  type SDKRequestTitleProps,
35
35
  SDKBreadcrumbs,
36
- Dropdown,
37
- DropdownTrigger,
38
- DropdownMenu,
39
- DropdownItem,
40
36
  SDKIcon,
41
37
  SDKOverview,
42
38
  SDKLanguageBlock,
43
39
  } from '@stainless-api/docs-ui/src/components';
40
+
41
+ import { Dropdown } from '@stainless-api/docs/components';
42
+
44
43
  import {
45
44
  BASE_PATH,
46
45
  EXCLUDE_LANGUAGES,
@@ -56,6 +55,7 @@ import { SnippetCode, SnippetContainer, SnippetRequestContainer } from '../compo
56
55
  import type { StlStarlightMiddleware } from '../middlewareBuilder/stainlessMiddleware';
57
56
  import { ComponentProvider } from '@stainless-api/docs-ui/src/contexts/component';
58
57
  import { AIDropdown } from '../../stl-docs/components/AIDropdown';
58
+ import { ChevronsUpDownIcon } from 'lucide-react';
59
59
 
60
60
  export function generateDocsRoutes(spec: SDKJSON.Spec) {
61
61
  const paths = generateRouteList({
@@ -177,30 +177,36 @@ export function SDKSelectReactComponent({
177
177
  }) {
178
178
  return (
179
179
  <Dropdown id={id} data-current-value={selected} className={className}>
180
- <DropdownTrigger
181
- className="dropdown-toggle"
182
- type="button"
183
- id="stl-docs-snippet-title-button"
184
- aria-expanded="false"
185
- withChevron
186
- >
187
- <SDKIcon language={getLanguageSnippet(selected)} size={16} />
188
- <span className="stl-snippet-dropdown-button-text">{LanguageNames[selected]}</span>
189
- </DropdownTrigger>
190
- <DropdownMenu
180
+ <Dropdown.Trigger>
181
+ <Dropdown.TriggerSelectedItem>
182
+ <Dropdown.Icon>
183
+ <SDKIcon language={getLanguageSnippet(selected)} />
184
+ </Dropdown.Icon>
185
+ <span className="stl-snippet-dropdown-button-text">{LanguageNames[selected]}</span>
186
+ </Dropdown.TriggerSelectedItem>
187
+ <Dropdown.TriggerIcon>
188
+ <ChevronsUpDownIcon size={16} />
189
+ </Dropdown.TriggerIcon>
190
+ </Dropdown.Trigger>
191
+ <Dropdown.Menu
191
192
  className="dropdown-menu stl-sdk-select-dropdown-menu"
192
- position="below"
193
193
  aria-labelledby="stl-docs-snippet-title-button"
194
194
  >
195
195
  {languages.map((item) => (
196
- <DropdownItem key={item} value={item} selected={item === selected}>
197
- <div>
196
+ <Dropdown.MenuItem key={item} value={item} isSelected={item === selected}>
197
+ <Dropdown.Icon>
198
198
  <SDKIcon language={getLanguageSnippet(item)} size={16} />
199
+ </Dropdown.Icon>
200
+ <Dropdown.MenuItemText>{LanguageNames[item]}</Dropdown.MenuItemText>
201
+ <Dropdown.MenuItemTemplate>
202
+ <Dropdown.Icon>
203
+ <SDKIcon language={getLanguageSnippet(item)} size={16} />
204
+ </Dropdown.Icon>
199
205
  <span className="stl-snippet-dropdown-button-text">{LanguageNames[item]}</span>
200
- </div>
201
- </DropdownItem>
206
+ </Dropdown.MenuItemTemplate>
207
+ </Dropdown.MenuItem>
202
208
  ))}
203
- </DropdownMenu>
209
+ </Dropdown.Menu>
204
210
  </Dropdown>
205
211
  );
206
212
  }
@@ -50,7 +50,7 @@ export function AIDropdown() {
50
50
  <React.Fragment key={group.reactKey}>
51
51
  {group.options.map((option) => (
52
52
  <DropdownButton.MenuItem value={option.id} isExternalLink={option.external} key={option.id}>
53
- <DropdownButton.MenuItemIcon>{iconMap[option.icon]}</DropdownButton.MenuItemIcon>
53
+ <DropdownButton.Icon>{iconMap[option.icon]}</DropdownButton.Icon>
54
54
  <ItemLabel label={option.label} />
55
55
  </DropdownButton.MenuItem>
56
56
  ))}
@@ -1,10 +1,6 @@
1
1
  ---
2
- import {
3
- Dropdown,
4
- DropdownTrigger,
5
- DropdownMenu,
6
- DropdownItem,
7
- } from '@stainless-api/docs-ui/src/components/dropdown';
2
+ import { Dropdown } from '@stainless-api/docs/components';
3
+ import { ChevronsUpDownIcon } from 'lucide-react';
8
4
 
9
5
  const options = [
10
6
  {
@@ -19,7 +15,7 @@ const options = [
19
15
  >
20
16
  <path
21
17
  d="M21 5C21 4.44771 20.5523 4 20 4H4C3.44772 4 3 4.44772 3 5V15C3 15.5523 3.44772 16 4 16H20C20.5523 16 21 15.5523 21 15V5ZM23 15C23 16.6569 21.6569 18 20 18H13V20H16C16.5523 20 17 20.4477 17 21C17 21.5523 16.5523 22 16 22H8C7.44772 22 7 21.5523 7 21C7 20.4477 7.44772 20 8 20H11V18H4C2.34315 18 1 16.6569 1 15V5C1 3.34315 2.34315 2 4 2H20C21.6569 2 23 3.34315 23 5V15Z"
22
- fill="#262626"></path></svg
18
+ fill="var(--stl-ui-foreground)"></path></svg
23
19
  >`,
24
20
  },
25
21
  {
@@ -34,7 +30,7 @@ const options = [
34
30
  >
35
31
  <path
36
32
  d="M11 22V20C11 19.4477 11.4477 19 12 19C12.5523 19 13 19.4477 13 20V22C13 22.5523 12.5523 23 12 23C11.4477 23 11 22.5523 11 22ZM5.63281 16.9531C6.02332 16.5626 6.65635 16.5626 7.04688 16.9531C7.4374 17.3436 7.4374 17.9767 7.04688 18.3672L5.6377 19.7773C5.24717 20.1679 4.61318 20.1679 4.22266 19.7773C3.83232 19.387 3.83259 18.7538 4.22266 18.3633L5.63281 16.9531ZM16.9531 16.9531C17.3191 16.5871 17.8982 16.5637 18.291 16.8838L18.3672 16.9531L19.7773 18.3633L19.8457 18.4385C20.1663 18.8313 20.1435 19.4111 19.7773 19.7773C19.4113 20.1433 18.8322 20.1658 18.4395 19.8457L18.3633 19.7773L16.9531 18.3672L16.8848 18.291C16.5643 17.8983 16.5871 17.3193 16.9531 16.9531ZM15 12C15 10.3431 13.6569 9 12 9C10.3431 9 9 10.3431 9 12C9 13.6569 10.3431 15 12 15C13.6569 15 15 13.6569 15 12ZM4 11C4.55228 11 5 11.4477 5 12C5 12.5523 4.55228 13 4 13H2C1.44772 13 1 12.5523 1 12C1 11.4477 1.44772 11 2 11H4ZM22 11C22.5523 11 23 11.4477 23 12C23 12.5523 22.5523 13 22 13H20C19.4477 13 19 12.5523 19 12C19 11.4477 19.4477 11 20 11H22ZM7.04688 5.63281C7.4374 6.02334 7.4374 6.65635 7.04688 7.04688C6.65634 7.43728 6.0233 7.43736 5.63281 7.04688L4.22266 5.63672L5.6377 4.22266L7.04688 5.63281ZM18.3633 4.22266C18.7538 3.83235 19.3869 3.83224 19.7773 4.22266C20.1678 4.61311 20.1677 5.24618 19.7773 5.63672L18.3672 7.04688C17.9767 7.4374 17.3436 7.4374 16.9531 7.04688C16.5626 6.65635 16.5626 6.02332 16.9531 5.63281L18.3633 4.22266ZM4.22266 4.22266C4.61318 3.83213 5.24717 3.83213 5.6377 4.22266L4.22266 5.63672C3.83254 5.24621 3.83238 4.61307 4.22266 4.22266ZM11 4V2C11 1.44772 11.4477 1 12 1C12.5523 1 13 1.44772 13 2V4C13 4.55228 12.5523 5 12 5C11.4477 5 11 4.55228 11 4ZM17 12C17 14.7614 14.7614 17 12 17C9.23858 17 7 14.7614 7 12C7 9.23858 9.23858 7 12 7C14.7614 7 17 9.23858 17 12Z"
37
- fill="#262626"></path></svg
33
+ fill="var(--stl-ui-foreground)"></path></svg
38
34
  >`,
39
35
  },
40
36
  {
@@ -49,46 +45,80 @@ const options = [
49
45
  >
50
46
  <path
51
47
  d="M9.13569 7.5C9.13569 6.37669 9.39314 5.28007 9.87397 4.28809C9.05381 4.51419 8.27063 4.87087 7.55561 5.34863C6.24009 6.22764 5.21483 7.47676 4.60932 8.93848C4.00386 10.4002 3.84465 12.0088 4.15327 13.5605C4.46195 15.1124 5.2239 16.5384 6.34272 17.6572C7.46154 18.776 8.88756 19.538 10.4394 19.8467C11.9912 20.1553 13.5997 19.9961 15.0615 19.3906C16.5232 18.7851 17.7723 17.7599 18.6513 16.4443C19.1292 15.7291 19.4848 14.9454 19.7109 14.125C18.719 14.6057 17.6231 14.8643 16.4999 14.8643C14.5469 14.8643 12.6739 14.088 11.2929 12.707C9.91191 11.326 9.13569 9.45304 9.13569 7.5ZM11.1357 7.5C11.1357 8.92261 11.701 10.287 12.707 11.293C13.7129 12.2989 15.0773 12.8643 16.4999 12.8643C17.9226 12.8643 19.287 12.2989 20.2929 11.293C20.5789 11.007 21.0091 10.9214 21.3828 11.0762C21.7564 11.231 22 11.5956 22 12C22 13.9778 21.4132 15.9112 20.3144 17.5557C19.2156 19.2001 17.6543 20.4824 15.8271 21.2393C13.9998 21.9961 11.9886 22.1935 10.0488 21.8076C8.10909 21.4217 6.32711 20.4697 4.92866 19.0713C3.53021 17.6728 2.57822 15.8909 2.19233 13.9512C1.80648 12.0114 2.00382 10.0001 2.76069 8.17285C3.51756 6.34567 4.79987 4.78434 6.44429 3.68555C8.08878 2.58673 10.0221 2 11.9999 2C12.4044 2 12.769 2.24359 12.9238 2.61719C13.0786 2.99086 12.993 3.42103 12.707 3.70703C11.701 4.71297 11.1357 6.07739 11.1357 7.5Z"
52
- fill="#262626"></path></svg
48
+ fill="var(--stl-ui-foreground)"></path></svg
53
49
  >`,
54
50
  },
55
51
  ];
56
52
  ---
57
53
 
58
- <div class="stldocs-root theme-select-root">
59
- <Dropdown className="theme-select-container" data-theme-select>
60
- <DropdownTrigger withChevron>
61
- <span class="stl-dropdown-icon">
62
- {options.map(({ icon }) => <Fragment set:html={icon} />)}
63
- </span>
64
- </DropdownTrigger>
65
- <DropdownMenu role="listbox">
66
- {
67
- options.map(({ value, label, icon }) => (
68
- <DropdownItem role="option" value={value}>
69
- <div>
70
- <span class="stl-dropdown-icon">
71
- <Fragment set:html={icon} />
72
- </span>
73
- <span class="stl-dropdown-label">{label}</span>
74
- </div>
75
- </DropdownItem>
76
- ))
77
- }
78
- </DropdownMenu>
79
- </Dropdown>
80
- </div>
54
+ <Dropdown data-theme-select>
55
+ <Dropdown.Trigger>
56
+ <Dropdown.TriggerSelectedItem>
57
+ <span class="stl-dropdown-icon"></span>
58
+ </Dropdown.TriggerSelectedItem>
59
+ <Dropdown.TriggerIcon>
60
+ <ChevronsUpDownIcon size={16} />
61
+ </Dropdown.TriggerIcon>
62
+ </Dropdown.Trigger>
63
+ <Dropdown.Menu>
64
+ {
65
+ options.map(({ value, label, icon }) => (
66
+ <Dropdown.MenuItem value={value}>
67
+ <Dropdown.Icon>
68
+ <Fragment set:html={icon} />
69
+ </Dropdown.Icon>
70
+ <Dropdown.MenuItemText>{label}</Dropdown.MenuItemText>
71
+ <Dropdown.MenuItemTemplate>
72
+ <span class="stl-dropdown-icon">
73
+ <Fragment set:html={icon} />
74
+ </span>
75
+ </Dropdown.MenuItemTemplate>
76
+ </Dropdown.MenuItem>
77
+ ))
78
+ }
79
+ </Dropdown.Menu>
80
+ </Dropdown>
81
81
 
82
- {/* Inlined to avoid FOUC. Uses global scope from `ThemeProvider.astro` */}
82
+ {/* Inlined to avoid FOUC. All theme selects are initialized here */}
83
83
  <script is:inline>
84
- document.addEventListener('astro:page-load', () => {
85
- StarlightThemeProvider.updatePickers();
86
- });
87
- StarlightThemeProvider.updatePickers();
84
+ window.didInitThemePickers = window.didInitThemePickers ?? false;
85
+
86
+ // Only run once.
87
+ if (!window.didInitThemePickers) {
88
+ window.didInitThemePickers = true;
89
+
90
+ // The stored theme will be either 'auto', 'light', 'dark' or null.
91
+ const storedTheme = typeof localStorage !== 'undefined' && localStorage.getItem('starlight-theme');
92
+
93
+ // This theme is either 'light' or 'dark'. It's used for setting the data-theme attribute.
94
+ const theme =
95
+ !storedTheme || storedTheme === 'auto'
96
+ ? window.matchMedia('(prefers-color-scheme: light)').matches
97
+ ? 'light'
98
+ : 'dark'
99
+ : storedTheme;
100
+
101
+ document.documentElement.dataset.theme = theme === 'light' ? 'light' : 'dark';
102
+
103
+ const themeSelects = document.querySelectorAll('[data-theme-select]');
104
+ const selectedThemeValue = storedTheme || 'auto';
105
+
106
+ themeSelects.forEach((select) => {
107
+ const tmpl = select.querySelector(`[data-value="${selectedThemeValue}"] template`);
108
+ const selectedSlot = select.querySelector('[data-part="trigger-selected"]');
109
+
110
+ selectedSlot.innerHTML = '';
111
+ if (tmpl) {
112
+ selectedSlot.appendChild(tmpl.content.cloneNode(true));
113
+ return;
114
+ }
115
+ });
116
+ }
88
117
  </script>
89
118
 
90
119
  <script>
91
120
  import { getPageLoadEvent } from '../../plugin/helpers/getPageLoadEvent';
121
+ import { initDropdown } from '@stainless-api/docs/components/scripts';
92
122
 
93
123
  const storageKey = 'starlight-theme';
94
124
 
@@ -100,82 +130,25 @@ const options = [
100
130
  const loadTheme = (): Theme => parseTheme(localStorage.getItem(storageKey));
101
131
 
102
132
  const storeTheme = (theme: Theme) => {
103
- localStorage.setItem(storageKey, theme === 'light' || theme === 'dark' ? theme : '');
133
+ localStorage.setItem(storageKey, parseTheme(theme));
104
134
  };
105
135
 
106
136
  const getPreferredColorScheme = (): Theme =>
107
137
  window.matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark';
108
138
 
109
- const highlightSelected = (doc: Document, theme: Theme) => {
110
- doc.querySelectorAll('.theme-select-container.stldocs-dropdown').forEach((container) => {
111
- const icons = container.querySelectorAll('.stl-dropdown-icon svg');
112
- icons.forEach((icon, index) => {
113
- const themeForIcon = ['auto', 'light', 'dark'][index];
114
- icon.classList.toggle('selected', theme === themeForIcon);
115
- });
116
-
117
- container.querySelectorAll('.stldocs-dropdown-item').forEach((option) => {
118
- const label = option.querySelector('.stl-dropdown-label')?.textContent?.toLowerCase();
119
- option.classList.toggle('selected', label === theme);
120
- });
121
-
122
- const menu = container.querySelector('.stldocs-dropdown-menu');
123
- menu?.classList.remove('open');
124
- });
125
- };
126
-
127
139
  const applyTheme = (doc: Document, theme: Theme) => {
128
140
  const effective = theme === 'auto' ? getPreferredColorScheme() : theme;
129
141
  doc.documentElement.dataset.theme = effective;
130
142
  storeTheme(theme);
131
- StarlightThemeProvider?.updatePickers?.(theme);
132
- highlightSelected(doc, theme);
133
143
  };
134
144
 
135
145
  function setupThemeMode(doc: Document) {
136
- // Attach to all instances of .theme-select-container.stldocs-dropdown. We need this because
137
- // the theme component is used in the mobile dropdown and the desktop sidebar.
138
- doc.querySelectorAll('.theme-select-container.stldocs-dropdown').forEach((container) => {
139
- const trigger = container.querySelector('.stldocs-dropdown-trigger');
140
- const menu = container.querySelector('.stldocs-dropdown-menu');
141
-
142
- if (trigger && menu) {
143
- trigger.addEventListener('click', (e) => {
144
- e.stopPropagation();
145
-
146
- doc.querySelectorAll('.theme-select-container .stldocs-dropdown-menu.open').forEach((otherMenu) => {
147
- if (otherMenu !== menu) otherMenu.classList.remove('open');
148
- });
149
-
150
- menu.classList.toggle('open');
151
- });
152
-
153
- container.querySelectorAll('.stldocs-dropdown-item').forEach((option) => {
154
- option.addEventListener('click', () => {
155
- const label = option.querySelector('.stl-dropdown-label')?.textContent?.toLowerCase() ?? '';
156
- if (['auto', 'light', 'dark'].includes(label)) {
157
- applyTheme(doc, label as Theme);
158
- }
159
- });
160
- });
161
- }
162
- });
163
-
164
- // Close dropdown if you click outside
165
- doc.addEventListener('click', (e) => {
166
- doc.querySelectorAll('.theme-select-container .stldocs-dropdown-menu.open').forEach((menu) => {
167
- if (e.target instanceof HTMLElement && !menu.contains(e.target)) {
168
- menu.classList.remove('open');
169
- }
170
- });
171
- });
172
-
173
- // React to system preference changes
174
146
  matchMedia('(prefers-color-scheme: light)').addEventListener('change', () => {
175
147
  if (loadTheme() === 'auto') applyTheme(doc, 'auto');
176
148
  });
177
149
 
178
150
  applyTheme(doc, loadTheme());
151
+ return loadTheme();
179
152
  }
180
153
 
181
154
  document.addEventListener('astro:before-swap', (event) => {
@@ -183,16 +156,19 @@ const options = [
183
156
  });
184
157
 
185
158
  document.addEventListener(getPageLoadEvent(), () => {
186
- setupThemeMode(document);
159
+ const initialTheme = setupThemeMode(document);
160
+ const themeSelects = document.querySelectorAll<HTMLElement>('[data-theme-select]');
161
+ themeSelects.forEach((select) => {
162
+ initDropdown({
163
+ root: select,
164
+ onSelect: (value: string) => applyTheme(document, value as Theme),
165
+ initialValue: initialTheme as string,
166
+ });
167
+ });
187
168
  });
188
169
  </script>
189
170
 
190
171
  <style is:global>
191
- .theme-select-root {
192
- background-color: unset;
193
- display: flex;
194
- align-items: center;
195
- }
196
172
  .stl-dropdown-icon svg {
197
173
  width: 16px;
198
174
  height: 16px;
@@ -202,14 +178,6 @@ const options = [
202
178
  }
203
179
  }
204
180
 
205
- .stldocs-dropdown-trigger .stl-dropdown-icon svg {
206
- display: none;
207
-
208
- &.selected {
209
- display: flex;
210
- }
211
- }
212
- .stldocs-root .stl-dropdown-icon,
213
181
  .stl-dropdown-icon {
214
182
  display: flex;
215
183
  align-items: center;
@@ -218,9 +186,7 @@ const options = [
218
186
  height: 16px;
219
187
  }
220
188
 
221
- .stldocs-root span.stl-dropdown-icon,
222
- span.stl-dropdown-icon,
223
- span.stl-dropdown-chevron {
189
+ span.stl-dropdown-icon {
224
190
  padding-left: 0;
225
191
  padding-right: 0%;
226
192
  }
@@ -1,2 +1 @@
1
1
  export * from '@stainless-api/ui-primitives';
2
- export * from '@stainless-api/ui-primitives/scripts';
@@ -1,77 +1,48 @@
1
1
  ---
2
- import {
3
- Dropdown,
4
- DropdownTrigger,
5
- DropdownMenu,
6
- DropdownItem,
7
- } from '@stainless-api/docs-ui/src/components/dropdown';
8
- import { Icon } from '@astrojs/starlight/components';
2
+ import { Dropdown } from '@stainless-api/docs/components';
9
3
  import { buildNavLinks } from './buildNavLinks';
10
-
11
- export interface Props {
12
- useHamburgerIcon?: boolean;
13
- }
14
-
15
- const { useHamburgerIcon = false } = Astro.props as Props;
16
- const BUTTON_ID = 'nav-dropdown-button';
4
+ import { ChevronsUpDownIcon } from 'lucide-react';
17
5
 
18
6
  const navLinks = buildNavLinks(Astro.locals.starlightRoute);
19
7
 
20
8
  const buttonText = (navLinks.find((item) => item.active) ?? navLinks[0]!).label;
21
9
  ---
22
10
 
23
- <div class="stldocs-root nav-dropdown-root">
24
- <Dropdown id="nav-dropdown">
25
- <DropdownTrigger
26
- className={`dropdown-toggle stldocs-button-tertiary nav-dropdown-button`}
27
- type="button"
28
- id={BUTTON_ID}
29
- aria-expanded="false"
30
- withChevron={!useHamburgerIcon}
31
- isIcon
32
- >
33
- {useHamburgerIcon ? <Icon name="bars" size="16px" /> : buttonText}
34
- </DropdownTrigger>
35
- <DropdownMenu className="dropdown-menu" position="below" aria-labelledby={BUTTON_ID}>
36
- {
37
- navLinks.map((item) => (
38
- <DropdownItem key={item.link} href={item.link} className="dropdown-item">
39
- {item.label}
40
- </DropdownItem>
41
- ))
42
- }
43
- </DropdownMenu>
44
- </Dropdown>
45
- </div>
11
+ <Dropdown id="nav-dropdown" className="nav-dropdown-root">
12
+ <Dropdown.Trigger>
13
+ <Dropdown.TriggerSelectedItem>{buttonText}</Dropdown.TriggerSelectedItem>
14
+ <Dropdown.TriggerIcon>
15
+ <ChevronsUpDownIcon size={16} />
16
+ </Dropdown.TriggerIcon>
17
+ </Dropdown.Trigger>
18
+ <Dropdown.Menu className="dropdown-menu">
19
+ {
20
+ navLinks.map((item) => (
21
+ <Dropdown.MenuItem
22
+ key={item.link}
23
+ href={item.link}
24
+ className="dropdown-item"
25
+ value={item.label}
26
+ isSelected={item.label === buttonText}
27
+ >
28
+ {item.label}
29
+ <Dropdown.MenuItemTemplate>{item.label}</Dropdown.MenuItemTemplate>
30
+ </Dropdown.MenuItem>
31
+ ))
32
+ }
33
+ </Dropdown.Menu>
34
+ </Dropdown>
46
35
 
47
36
  <script>
48
- import { initDropdown } from '@stainless-api/docs-ui/src/components/scripts/dropdown';
37
+ import { initDropdown } from '@stainless-api/docs/components/scripts';
49
38
  import { getPageLoadEvent } from '../../../plugin/helpers/getPageLoadEvent';
50
39
 
51
40
  document.addEventListener(getPageLoadEvent(), () => {
52
41
  initDropdown({
53
- dropdownId: 'nav-dropdown',
54
- isFixed: true,
42
+ root: document.getElementById('nav-dropdown'),
55
43
  });
56
44
  });
57
45
  </script>
58
- <style is:global>
59
- .nav-dropdown-root .dropdown-menu.stldocs-dropdown-menu {
60
- display: none;
61
- position: fixed;
62
- z-index: 1000;
63
- width: 200px;
64
- margin: 0.125rem 0 0;
65
- font-size: 1rem;
66
- text-align: left;
67
- list-style: none;
68
- background-clip: padding-box;
69
- }
70
-
71
- .nav-dropdown-root .dropdown-menu.open {
72
- display: block;
73
- }
74
- </style>
75
46
 
76
47
  <style>
77
48
  :root {
@@ -98,9 +69,4 @@ const buttonText = (navLinks.find((item) => item.active) ?? navLinks[0]!).label;
98
69
  .nav-dropdown-root {
99
70
  background-color: var(--sl-color-bg-nav);
100
71
  }
101
-
102
- .nav-dropdown-button {
103
- color: var(--sl-color-text);
104
- font-weight: 500;
105
- }
106
72
  </style>
@@ -7,80 +7,98 @@ import clsx from 'clsx';
7
7
  const navLinks = buildNavLinks(Astro.locals.starlightRoute);
8
8
  ---
9
9
 
10
- <div id="nav-links-container">
11
- <ul class="nav-links" id="nav-links-list">
12
- <li class="mobile-menu-item" data-mobile-only>
13
- <NavDropdown />
14
- </li>
15
- {
16
- navLinks.map((item) => (
17
- <li data-desktop-only>
18
- <Button
19
- href={item.link}
20
- className={clsx('nav-link')}
21
- variant={item.active ? 'accent-muted' : 'ghost'}
22
- >
23
- <span>{item.label}</span>
24
- </Button>
25
- </li>
26
- ))
27
- }
28
- </ul>
29
- <!-- The test container is used to measure the width of the nav links and see if they fit -->
30
- <!-- TODO: probably want to clean this up bc it's duplicated code -->
31
- <ul class="nav-links" id="nav-links-test-container">
32
- {
33
- navLinks.map((item) => (
34
- <li>
35
- <Button href={item.link} className={clsx('nav-link')} variant={item.active ? 'accent' : 'ghost'}>
36
- <span>{item.label}</span>
37
- </Button>
38
- </li>
39
- ))
40
- }
41
- </ul>
10
+ <div id="nav-links-container" class="nav-links-container">
11
+ <div class="desktop-nav-links-container">
12
+ <ul class="nav-links" id="nav-links-list">
13
+ {
14
+ navLinks.map((item) => (
15
+ <li data-desktop-only>
16
+ <Button
17
+ href={item.link}
18
+ className={clsx('nav-link')}
19
+ variant={item.active ? 'accent-muted' : 'ghost'}
20
+ >
21
+ <span>{item.label}</span>
22
+ </Button>
23
+ </li>
24
+ ))
25
+ }
26
+ </ul>
27
+ </div>
28
+ <div class="mobile-nav-dropdown" data-mobile-only>
29
+ <NavDropdown />
30
+ </div>
42
31
  </div>
43
32
 
44
33
  <script>
45
34
  import { getPageLoadEvent } from '../../../plugin/helpers/getPageLoadEvent';
46
35
 
47
- function getRequiredWidth() {
48
- const navLinksTestContainer = document.getElementById('nav-links-test-container');
49
- if (!navLinksTestContainer) return 1000000; // fallback to a large number
36
+ // We need to measure the nav links to see if they fit. We create an offscreen hidden clone for measurement.
37
+ function createOffscreenClone(element: HTMLElement): HTMLElement {
38
+ const clone = element.cloneNode(true) as HTMLElement;
39
+ clone.removeAttribute('id');
40
+ clone.id = 'cloned-nav-links-measurement';
41
+ clone.style.position = 'absolute';
42
+ clone.style.visibility = 'hidden';
43
+ clone.style.top = '0';
44
+ clone.style.left = '0';
45
+ clone.style.width = 'auto';
46
+ clone.style.whiteSpace = 'nowrap';
47
+ clone.style.pointerEvents = 'none';
48
+ clone.style.zIndex = '-9999';
49
+
50
+ clone.querySelectorAll('[data-desktop-only], [data-mobile-only]').forEach((el) => {
51
+ el.removeAttribute('data-desktop-only');
52
+ });
50
53
 
51
- const rw = navLinksTestContainer.scrollWidth;
52
- navLinksTestContainer.remove();
53
- return rw;
54
+ document.body.appendChild(clone);
55
+ return clone;
54
56
  }
55
57
 
56
58
  function initNavbar() {
57
- const masterContainer = document.getElementById('nav-links-container');
58
- if (!masterContainer) return;
59
+ const navLinksContainer = document.getElementById('nav-links-container');
60
+ if (!navLinksContainer) {
61
+ console.error(`NavTabs: #nav-links-container not found`);
62
+ return;
63
+ }
64
+
65
+ const navLinksList = document.getElementById('nav-links-list');
66
+ if (!navLinksList) {
67
+ console.error(`NavTabs: #nav-links-list not found`);
68
+ return;
69
+ }
59
70
 
60
- const navLinksContainer = document.getElementById('nav-links-list');
61
- if (!navLinksContainer) return;
71
+ const measurementClone = createOffscreenClone(navLinksList);
72
+ const requiredWidth = measurementClone.clientWidth;
62
73
 
63
- const requiredWidth = getRequiredWidth();
74
+ let currentMode: 'desktop' | 'mobile' | null = null;
64
75
 
65
76
  function checkIfFits() {
66
- if (!masterContainer) return;
67
77
  if (!navLinksContainer) return;
68
- const availableWidth = masterContainer.clientWidth;
69
- const canFit = availableWidth >= requiredWidth;
78
+
79
+ // 16px buffer helps it feel less crowded before switching modes
80
+ const bufferSpace = 16;
81
+ const availableWidth = navLinksContainer.clientWidth;
82
+ const canFit = availableWidth - bufferSpace >= requiredWidth;
70
83
 
71
84
  const mode = canFit ? 'desktop' : 'mobile';
72
85
 
73
- localStorage.setItem('stl-nav-links-mode', mode);
86
+ // Only update if mode actually changed
87
+ if (mode === currentMode) return;
74
88
 
75
- document.documentElement.classList.remove('stl-nav-links-mode-desktop');
76
- document.documentElement.classList.remove('stl-nav-links-mode-mobile');
89
+ currentMode = mode;
77
90
 
91
+ localStorage.setItem('stl-nav-links-mode', mode);
92
+
93
+ document.documentElement.classList.remove('stl-nav-links-mode-desktop', 'stl-nav-links-mode-mobile');
78
94
  document.documentElement.classList.add('stl-nav-links-mode-' + mode);
79
95
  }
80
96
 
81
- window.addEventListener('resize', () => {
97
+ const resizeObserver = new ResizeObserver(() => {
82
98
  checkIfFits();
83
99
  });
100
+
101
+ resizeObserver.observe(navLinksContainer);
84
102
  checkIfFits();
85
103
  }
86
104
 
@@ -109,18 +127,17 @@ const navLinks = buildNavLinks(Astro.locals.starlightRoute);
109
127
  </style>
110
128
 
111
129
  <style>
112
- /* :root {
113
- --menu-font-size: calc(var(--sl-text-body));
114
- } */
130
+ .nav-links-container {
131
+ flex-grow: 1;
132
+ margin-left: 1rem;
133
+ margin-right: 1rem;
134
+ }
115
135
 
116
- #nav-links-container {
117
- visibility: hidden;
136
+ .desktop-nav-links-container {
118
137
  display: flex;
119
138
  flex-grow: 1;
120
139
  justify-content: flex-start;
121
140
  overflow: hidden;
122
- margin-left: 1rem;
123
- margin-right: 1rem;
124
141
  }
125
142
 
126
143
  .nav-links {
@@ -133,13 +150,6 @@ const navLinks = buildNavLinks(Astro.locals.starlightRoute);
133
150
  overflow: hidden;
134
151
  }
135
152
 
136
- #nav-links-test-container {
137
- position: absolute;
138
- top: 0;
139
- left: 0;
140
- visibility: hidden;
141
- }
142
-
143
153
  .nav-links li {
144
154
  display: flex;
145
155
  align-items: center;
@@ -156,8 +166,12 @@ const navLinks = buildNavLinks(Astro.locals.starlightRoute);
156
166
  }
157
167
  }
158
168
 
169
+ .mobile-nav-dropdown {
170
+ flex-grow: 1;
171
+ }
172
+
159
173
  @media (min-width: 50rem) {
160
- #nav-links-container {
174
+ .desktop-nav-links-container {
161
175
  display: flex;
162
176
  visibility: visible;
163
177
  }
@@ -0,0 +1 @@
1
+ export * from '@stainless-api/ui-primitives/scripts';
@@ -17,8 +17,7 @@ starlight-theme-select select {
17
17
  overflow: hidden;
18
18
  }
19
19
 
20
- .custom-select-wrapper select,
21
- .stldocs-button-secondary {
20
+ .custom-select-wrapper select {
22
21
  background-color: var(--sl-color-bg-ui);
23
22
  border: 1px solid var(--sl-color-hairline);
24
23
  border-radius: var(--sl-button-border-radius);
package/styles/page.css CHANGED
@@ -23,6 +23,8 @@
23
23
 
24
24
  .mobile-preferences {
25
25
  margin-top: 1rem;
26
+ display: flex;
27
+ justify-content: flex-end;
26
28
  }
27
29
  }
28
30
 
@@ -1,9 +1,12 @@
1
- .stldocs-root {
2
- .stldocs-dropdown-menu.stl-sdk-select-dropdown-menu {
3
- min-width: 200px;
4
- }
5
- }
6
1
  .stl-sdk-select {
2
+ .stl-ui-dropdown {
3
+ width: 100%;
4
+
5
+ .stl-ui-dropdown__button {
6
+ width: 100%;
7
+ justify-content: space-between;
8
+ }
9
+ }
7
10
  .stldocs-icon.http path {
8
11
  fill: var(--stldocs-color-text);
9
12
  }
package/styles/search.css CHANGED
@@ -22,6 +22,7 @@ site-search {
22
22
 
23
23
  &:hover {
24
24
  border-color: var(--sl-color-hairline-shade);
25
+ background: color-mix(in oklch, var(--stl-ui-foreground) 5%, var(--stl-ui-inverse-foreground));
25
26
  }
26
27
 
27
28
  span {
@@ -36,10 +37,6 @@ site-search {
36
37
  width: var(--sl-text-sm);
37
38
  height: var(--sl-text-sm);
38
39
  }
39
-
40
- &.stldocs-button {
41
- padding: 0;
42
- }
43
40
  }
44
41
 
45
42
  button > kbd {
@@ -67,11 +64,6 @@ site-search {
67
64
  max-width: 30rem;
68
65
  justify-content: start;
69
66
 
70
- &.stldocs-button {
71
- padding-inline-start: var(--sl-button-padding-x);
72
- padding-inline-end: var(--sl-button-padding-x);
73
- }
74
-
75
67
  > :last-child {
76
68
  margin-inline-start: auto;
77
69
  }
@@ -63,11 +63,7 @@
63
63
  color: var(--stl-ui-foreground);
64
64
  }
65
65
  }
66
- .stl-sdk-select .stldocs-dropdown-trigger {
67
- /* dropdown should match link padding, accounting for 1px border */
68
- padding: calc(var(--stl-sidebar-item-padding-block) - 1px)
69
- calc(var(--stl-sidebar-item-padding-inline) - 1px);
70
- }
66
+
71
67
  li a {
72
68
  color: var(--stl-ui-foreground-secondary);
73
69
  font-weight: 400;
@@ -217,10 +213,6 @@
217
213
  position: relative;
218
214
  }
219
215
 
220
- .stl-sdk-select .dropdown-toggle {
221
- width: 100%;
222
- }
223
-
224
216
  .stl-sidebar-header-links {
225
217
  display: flex;
226
218
  gap: 1rem;