@redocly/theme 0.70.0-next.5 → 0.70.0-next.7

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 (99) hide show
  1. package/lib/components/Buttons/ConnectMCPButton.js +8 -13
  2. package/lib/components/Catalog/CatalogEntity/CatalogEntityGraph/CatalogEntityRelationsGraph.lazy.js +1 -1
  3. package/lib/components/Catalog/CatalogFilter/CatalogFilterDateRange.js +2 -1
  4. package/lib/components/Dropdown/Dropdown.d.ts +4 -2
  5. package/lib/components/Dropdown/Dropdown.js +28 -8
  6. package/lib/components/Filter/FilterDateRange.js +36 -2
  7. package/lib/components/Portal/Portal.js +37 -5
  8. package/lib/components/Search/SearchAiActionButtons.js +8 -3
  9. package/lib/components/Search/SearchAiActivity.d.ts +10 -0
  10. package/lib/components/Search/SearchAiActivity.js +278 -0
  11. package/lib/components/Search/SearchAiDialog.js +5 -1
  12. package/lib/components/Search/SearchAiMessage.d.ts +2 -1
  13. package/lib/components/Search/SearchAiMessage.js +20 -108
  14. package/lib/components/Search/SearchDialog.js +10 -0
  15. package/lib/components/Search/SearchItem.js +1 -8
  16. package/lib/components/Search/variables.js +15 -2
  17. package/lib/components/Select/SelectInput.js +2 -8
  18. package/lib/components/SplitButton/SplitButton.js +37 -3
  19. package/lib/core/constants/mcp.d.ts +2 -2
  20. package/lib/core/constants/mcp.js +3 -8
  21. package/lib/core/constants/search.js +5 -1
  22. package/lib/core/contexts/PortalContainerContext.d.ts +2 -0
  23. package/lib/core/contexts/PortalContainerContext.js +7 -0
  24. package/lib/core/hooks/index.d.ts +2 -0
  25. package/lib/core/hooks/index.js +2 -0
  26. package/lib/core/hooks/menu/use-collapse.js +1 -1
  27. package/lib/core/hooks/search/use-persisted-quick-filter.d.ts +21 -0
  28. package/lib/core/hooks/search/use-persisted-quick-filter.js +92 -0
  29. package/lib/core/hooks/search/use-search-ai-activity.d.ts +11 -0
  30. package/lib/core/hooks/search/use-search-ai-activity.js +15 -0
  31. package/lib/core/hooks/use-connect-mcp-button.js +2 -1
  32. package/lib/core/hooks/use-full-height.d.ts +1 -1
  33. package/lib/core/hooks/use-full-height.js +8 -8
  34. package/lib/core/styles/global.d.ts +2 -0
  35. package/lib/core/styles/global.js +80 -76
  36. package/lib/core/styles/index.d.ts +1 -0
  37. package/lib/core/styles/index.js +3 -1
  38. package/lib/core/styles/search-ai-appear.d.ts +1 -0
  39. package/lib/core/styles/search-ai-appear.js +18 -0
  40. package/lib/core/types/dropdown.d.ts +1 -0
  41. package/lib/core/types/dropdown.js +3 -0
  42. package/lib/core/types/index.d.ts +1 -0
  43. package/lib/core/types/l10n.d.ts +1 -1
  44. package/lib/core/types/mcp.d.ts +2 -1
  45. package/lib/core/types/search.d.ts +23 -0
  46. package/lib/core/utils/assign-ref.d.ts +2 -0
  47. package/lib/core/utils/assign-ref.js +12 -0
  48. package/lib/core/utils/content-segments.d.ts +12 -1
  49. package/lib/core/utils/content-segments.js +90 -5
  50. package/lib/core/utils/dropdown.d.ts +2 -0
  51. package/lib/core/utils/dropdown.js +12 -0
  52. package/lib/core/utils/index.d.ts +2 -0
  53. package/lib/core/utils/index.js +2 -0
  54. package/lib/core/utils/mcp.d.ts +2 -1
  55. package/lib/core/utils/mcp.js +5 -0
  56. package/lib/core/utils/search-ai-activity.d.ts +4 -0
  57. package/lib/core/utils/search-ai-activity.js +83 -0
  58. package/lib/markdoc/tags/connect-mcp.js +15 -0
  59. package/package.json +3 -2
  60. package/src/components/Buttons/ConnectMCPButton.tsx +8 -14
  61. package/src/components/Catalog/CatalogEntity/CatalogEntityGraph/CatalogEntityRelationsGraph.lazy.tsx +3 -1
  62. package/src/components/Catalog/CatalogFilter/CatalogFilterDateRange.tsx +3 -2
  63. package/src/components/Dropdown/Dropdown.tsx +28 -6
  64. package/src/components/Filter/FilterDateRange.tsx +3 -2
  65. package/src/components/Portal/Portal.tsx +4 -2
  66. package/src/components/Search/SearchAiActionButtons.tsx +31 -19
  67. package/src/components/Search/SearchAiActivity.tsx +318 -0
  68. package/src/components/Search/SearchAiDialog.tsx +4 -3
  69. package/src/components/Search/SearchAiMessage.tsx +27 -135
  70. package/src/components/Search/SearchDialog.tsx +11 -0
  71. package/src/components/Search/SearchItem.tsx +2 -9
  72. package/src/components/Search/variables.ts +15 -2
  73. package/src/components/Select/SelectInput.tsx +2 -8
  74. package/src/components/SplitButton/SplitButton.tsx +8 -2
  75. package/src/core/constants/mcp.ts +3 -9
  76. package/src/core/constants/search.ts +5 -1
  77. package/src/core/contexts/PortalContainerContext.tsx +4 -0
  78. package/src/core/hooks/__mocks__/index.ts +2 -0
  79. package/src/core/hooks/index.ts +2 -0
  80. package/src/core/hooks/menu/use-collapse.ts +1 -1
  81. package/src/core/hooks/search/use-persisted-quick-filter.ts +125 -0
  82. package/src/core/hooks/search/use-search-ai-activity.ts +31 -0
  83. package/src/core/hooks/use-connect-mcp-button.ts +7 -2
  84. package/src/core/hooks/use-full-height.ts +8 -8
  85. package/src/core/styles/global.ts +80 -75
  86. package/src/core/styles/index.ts +2 -1
  87. package/src/core/styles/search-ai-appear.ts +16 -0
  88. package/src/core/types/dropdown.ts +1 -0
  89. package/src/core/types/index.ts +1 -0
  90. package/src/core/types/l10n.ts +4 -1
  91. package/src/core/types/mcp.ts +3 -1
  92. package/src/core/types/search.ts +21 -0
  93. package/src/core/utils/assign-ref.ts +9 -0
  94. package/src/core/utils/content-segments.ts +98 -6
  95. package/src/core/utils/dropdown.ts +14 -0
  96. package/src/core/utils/index.ts +2 -0
  97. package/src/core/utils/mcp.ts +7 -1
  98. package/src/core/utils/search-ai-activity.ts +110 -0
  99. package/src/markdoc/tags/connect-mcp.ts +16 -1
@@ -6,6 +6,7 @@ import type { SelectOption } from '@redocly/theme/core/types';
6
6
  import { Tag } from '@redocly/theme/components/Tag/Tag';
7
7
  import { CloseIcon } from '@redocly/theme/icons/CloseIcon/CloseIcon';
8
8
  import { Button } from '@redocly/theme/components/Button/Button';
9
+ import { assignRef } from '@redocly/theme/core/utils';
9
10
 
10
11
  type SelectInputProps<T> = {
11
12
  id?: string;
@@ -125,14 +126,7 @@ export function SelectInput<T>(props: SelectInputProps<T>): React.ReactNode {
125
126
  if (!input) return;
126
127
 
127
128
  inputRef.current = input;
128
-
129
- if (!forwardedInputRef) return;
130
-
131
- if (typeof forwardedInputRef === 'function') {
132
- forwardedInputRef(input);
133
- } else {
134
- forwardedInputRef.current = input;
135
- }
129
+ assignRef(forwardedInputRef, input);
136
130
  }}
137
131
  $width={multiple ? (!searchValue && selectedOptions.length ? '10px' : 'auto') : '100%'}
138
132
  tabIndex={0}
@@ -1,4 +1,4 @@
1
- import React from 'react';
1
+ import React, { useRef } from 'react';
2
2
  import styled from 'styled-components';
3
3
 
4
4
  import type { ComponentProps, JSX } from 'react';
@@ -45,9 +45,14 @@ export function SplitButton(props: SplitButtonProps): JSX.Element {
45
45
  } = props;
46
46
 
47
47
  const hasMenu = typeof children === 'function' || React.Children.toArray(children).length > 0;
48
+ const wrapperRef = useRef<HTMLDivElement>(null);
48
49
 
49
50
  return (
50
- <SplitButtonWrapper data-component-name="SplitButton/SplitButton" className={className}>
51
+ <SplitButtonWrapper
52
+ data-component-name="SplitButton/SplitButton"
53
+ className={className}
54
+ ref={wrapperRef}
55
+ >
51
56
  <ButtonGroup variant={variant} size={size}>
52
57
  {button}
53
58
  {hasMenu ? (
@@ -56,6 +61,7 @@ export function SplitButton(props: SplitButtonProps): JSX.Element {
56
61
  placement={placement}
57
62
  alignment={alignment}
58
63
  portalled={portalled}
64
+ alignmentAnchorRef={wrapperRef}
59
65
  withArrow
60
66
  onOpen={onOpen}
61
67
  onClose={onClose}
@@ -1,15 +1,9 @@
1
- import type { MCPOption } from '../types/mcp';
2
-
3
1
  export const DEFAULT_MCP_SERVER_NAME = 'mcp-server';
4
2
 
5
3
  export const MCP_ROUTE = '/mcp';
6
4
 
7
5
  export const PUBLIC_MCP_ROUTE = '/mcp-public';
8
6
 
9
- export const DEFAULT_MCP_OPTIONS: MCPOption[] = [
10
- 'cursor',
11
- 'vscode',
12
- 'claude-code',
13
- 'codex',
14
- 'copy',
15
- ];
7
+ export const MCP_OPTIONS = ['cursor', 'vscode', 'claude-code', 'codex', 'copy'] as const;
8
+
9
+ export const DEFAULT_MCP_OPTIONS = [...MCP_OPTIONS];
@@ -53,7 +53,7 @@ export const TOOL_CALL_DISPLAY_TEXT: Partial<
53
53
  > = {
54
54
  [ToolCallName.SearchDocumentation]: {
55
55
  inProgressText: 'Searching documentation...',
56
- completedText: 'Search completed',
56
+ completedText: 'Searched the documentation',
57
57
  },
58
58
  [ToolCallName.ListApis]: {
59
59
  inProgressText: 'Browsing APIs...',
@@ -75,6 +75,10 @@ export const TOOL_CALL_DISPLAY_TEXT: Partial<
75
75
  inProgressText: 'Reading API description...',
76
76
  completedText: 'Read API description',
77
77
  },
78
+ [ToolCallName.Execute]: {
79
+ inProgressText: 'Running code...',
80
+ completedText: 'Ran code',
81
+ },
78
82
  [ToolCallName.DescribeTools]: {
79
83
  inProgressText: 'Discovering available tools...',
80
84
  completedText: 'Discovered available tools',
@@ -0,0 +1,4 @@
1
+ import { createContext } from 'react';
2
+
3
+ /** Where theme portals mount; set it inside a shadow root (the AI assistant web component), else `document.body`. */
4
+ export const PortalContainerContext = createContext<HTMLElement | null>(null);
@@ -23,6 +23,8 @@ export * from '../menu/use-mobile-menu-levels';
23
23
  export * from '../feedback/use-report-dialog';
24
24
  export * from '../use-product-picker';
25
25
  export * from '../search/use-search-dialog';
26
+ export * from '../search/use-search-ai-activity';
27
+ export * from '../search/use-persisted-quick-filter';
26
28
  export * from '../use-language-picker';
27
29
  export * from './use-element-size';
28
30
  export * from './use-time-ago';
@@ -30,6 +30,8 @@ export * from './use-product-picker';
30
30
  export * from './search/use-search-dialog';
31
31
  export * from './search/use-search-filter';
32
32
  export * from './search/use-feedback-tooltip';
33
+ export * from './search/use-search-ai-activity';
34
+ export * from './search/use-persisted-quick-filter';
33
35
  export * from './use-controlled-state';
34
36
  export * from './use-codeblock-tabs-controls';
35
37
  export * from './code-walkthrough/use-code-walkthrough';
@@ -207,5 +207,5 @@ export function useEvent<T extends (...args: any[]) => any>(callback?: T): T {
207
207
  ref.current = callback;
208
208
  });
209
209
 
210
- return useCallback(((...args: Parameters<T>) => ref.current?.(...args)) as T, []);
210
+ return useCallback((...args: Parameters<T>) => ref.current?.(...args), []) as T;
211
211
  }
@@ -0,0 +1,125 @@
1
+ import { useEffect, useRef, useState } from 'react';
2
+
3
+ import type { Dispatch, SetStateAction } from 'react';
4
+ import type { SearchFacet, SearchFilterItem } from '../../types/search';
5
+
6
+ import { createStore, useStore } from '../use-store';
7
+
8
+ type StoredQuickFilter = Pick<SearchFilterItem, 'field' | 'values'>;
9
+
10
+ const quickFilterStore = createStore<StoredQuickFilter | null>({
11
+ storageKey: 'searchQuickFilter',
12
+ storageType: 'sessionStorage',
13
+ });
14
+
15
+ const findQuickFilter = (filter: SearchFilterItem[]) => filter.find((item) => item.isQuickFilter);
16
+
17
+ const withQuickFilter = (filter: SearchFilterItem[], quickFilter: StoredQuickFilter) => [
18
+ ...filter.filter((item) => !item.isQuickFilter),
19
+ ...(quickFilter.values.length ? [{ ...quickFilter, isQuickFilter: true }] : []),
20
+ ];
21
+
22
+ const groupFacetValues = (facets: SearchFacet[], field: string) => {
23
+ const facet = facets.find((item) => item.field === field);
24
+ const values = facet?.values ?? [];
25
+ return {
26
+ present: facet !== undefined,
27
+ values: values.map((item) => (typeof item === 'string' ? item : item.value)),
28
+ counted: values.some((item) => typeof item !== 'string' && item.isCounterVisible),
29
+ };
30
+ };
31
+
32
+ const isRestorable = (
33
+ stored: StoredQuickFilter | null,
34
+ groupField: string,
35
+ ): stored is StoredQuickFilter =>
36
+ !!stored &&
37
+ stored.field === groupField &&
38
+ Array.isArray(stored.values) &&
39
+ stored.values.length > 0 &&
40
+ stored.values.every((value) => typeof value === 'string');
41
+
42
+ type UsePersistedQuickFilterOptions = {
43
+ filter: SearchFilterItem[];
44
+ setFilter: Dispatch<SetStateAction<SearchFilterItem[]>>;
45
+ facets: SearchFacet[];
46
+ groupField: string;
47
+ query: string;
48
+ isSearchLoading: boolean;
49
+ searchError: string | null;
50
+ enabled?: boolean;
51
+ };
52
+
53
+ /**
54
+ * Remembers the quick filter on `groupField` for the browser tab and restores it on mount unless
55
+ * one is already selected. Restored values are dropped once known not to exist: absent from the
56
+ * full value list, or from the `groupField` facet left by the first search that finishes without
57
+ * error on an empty query (the restored value is that search's filter, so a value without
58
+ * documents is not counted). Does nothing while `enabled` is false.
59
+ */
60
+ export function usePersistedQuickFilter({
61
+ filter,
62
+ setFilter,
63
+ facets,
64
+ groupField,
65
+ query,
66
+ isSearchLoading,
67
+ searchError,
68
+ enabled = true,
69
+ }: UsePersistedQuickFilterOptions): void {
70
+ const [stored, setStored] = useStore(quickFilterStore, null);
71
+ const [initial] = useState(() =>
72
+ enabled && isRestorable(stored, groupField) && !findQuickFilter(filter) ? stored.values : null,
73
+ );
74
+ const phaseRef = useRef<'restoring' | 'validating' | 'done'>(initial ? 'restoring' : 'done');
75
+ const searchedRef = useRef(false);
76
+
77
+ useEffect(() => {
78
+ if (!initial) return;
79
+ // oxlint-disable-next-line react-you-might-not-need-an-effect/no-pass-data-to-parent -- the filter state belongs to the product's `useSearch` and starts empty on every mount
80
+ setFilter((current) => withQuickFilter(current, { field: groupField, values: initial }));
81
+ // eslint-disable-next-line react-hooks/exhaustive-deps -- applies only to the initial state
82
+ }, []);
83
+
84
+ useEffect(() => {
85
+ if (!enabled) return;
86
+ const quickFilter = findQuickFilter(filter);
87
+ // The commit that starts the restore still sees the empty initial filter.
88
+ if (phaseRef.current === 'restoring') {
89
+ if (!quickFilter) return;
90
+ phaseRef.current = 'validating';
91
+ }
92
+ if (phaseRef.current === 'validating' && initial) {
93
+ if (isSearchLoading) searchedRef.current = true;
94
+ const settled = searchedRef.current && !isSearchLoading && !searchError && !query.trim();
95
+ const { present, values: known, counted } = groupFacetValues(facets, groupField);
96
+ if ((known.length && !counted) || (settled && present)) {
97
+ phaseRef.current = 'done';
98
+ const values = quickFilter?.values.filter(
99
+ (value) => known.includes(value) || !initial.includes(value),
100
+ );
101
+ if (quickFilter && values && values.length !== quickFilter.values.length) {
102
+ // oxlint-disable-next-line react-you-might-not-need-an-effect/no-pass-data-to-parent -- the facets arrive after the restore
103
+ setFilter((current) => withQuickFilter(current, { field: groupField, values }));
104
+ return;
105
+ }
106
+ }
107
+ }
108
+ const next = quickFilter ? { field: quickFilter.field, values: quickFilter.values } : null;
109
+ if (JSON.stringify(next) !== JSON.stringify(stored)) {
110
+ setStored(next);
111
+ }
112
+ }, [
113
+ enabled,
114
+ filter,
115
+ facets,
116
+ groupField,
117
+ query,
118
+ isSearchLoading,
119
+ searchError,
120
+ initial,
121
+ stored,
122
+ setFilter,
123
+ setStored,
124
+ ]);
125
+ }
@@ -0,0 +1,31 @@
1
+ import type { SearchAiActivityStatus, SearchAiActivityStep, ToolCall } from '../../types/search';
2
+
3
+ import { useThemeHooks } from '../use-theme-hooks';
4
+ import { getSearchAiActivityStatus, getSearchAiActivityStep } from '../../utils/search-ai-activity';
5
+
6
+ type UseSearchAiActivityParams = {
7
+ toolCalls: ToolCall[];
8
+ isActive: boolean;
9
+ };
10
+
11
+ type UseSearchAiActivityReturn = {
12
+ status: SearchAiActivityStatus;
13
+ steps: SearchAiActivityStep[];
14
+ };
15
+
16
+ export function useSearchAiActivity({
17
+ toolCalls,
18
+ isActive,
19
+ }: UseSearchAiActivityParams): UseSearchAiActivityReturn {
20
+ const { useTranslate } = useThemeHooks();
21
+ const { translate } = useTranslate();
22
+
23
+ const runningToolCall = isActive ? toolCalls.find((toolCall) => !toolCall.result) : undefined;
24
+
25
+ return {
26
+ status: getSearchAiActivityStatus(toolCalls, isActive, runningToolCall, translate),
27
+ steps: toolCalls.map((toolCall) =>
28
+ getSearchAiActivityStep(toolCall, isActive && !toolCall.result, translate),
29
+ ),
30
+ };
31
+ }
@@ -7,7 +7,7 @@ import { useThemeHooks } from './use-theme-hooks';
7
7
  import { useMCPConfig } from './use-mcp-config';
8
8
  import { ClipboardService } from '../utils/clipboard-service';
9
9
  import { DEFAULT_MCP_OPTIONS } from '../constants/mcp';
10
- import { generateClaudeCodeCommand, generateCodexCommand } from '../utils/mcp';
10
+ import { generateClaudeCodeCommand, generateCodexCommand, isMCPOption } from '../utils/mcp';
11
11
  import { getBaseDataAttributes } from '../utils/telemetry/get-base-data-attributes';
12
12
 
13
13
  export type McpButtonOptions = {
@@ -80,7 +80,12 @@ export function useConnectMCPButton({
80
80
  [translate],
81
81
  );
82
82
 
83
- const visibleOptions = useMemo(() => options.filter(Boolean), [options]);
83
+ // Markdoc reports attribute errors but still renders the tag, so `options` can be any value.
84
+ const visibleOptions = useMemo(
85
+ () =>
86
+ Array.isArray(options) ? [...new Set(options.filter(isMCPOption))] : [...DEFAULT_MCP_OPTIONS],
87
+ [options],
88
+ );
84
89
 
85
90
  return {
86
91
  copiedOption,
@@ -3,9 +3,9 @@ import throttle from 'lodash.throttle';
3
3
 
4
4
  import type { RefObject } from 'react';
5
5
 
6
- export function useFullHeight(element: RefObject<HTMLElement | null>): void {
6
+ export function useFullHeight(elementRef: RefObject<HTMLElement | null>): void {
7
7
  const handler = useCallback(() => {
8
- if (!element.current) {
8
+ if (!elementRef.current) {
9
9
  return;
10
10
  }
11
11
 
@@ -14,19 +14,19 @@ export function useFullHeight(element: RefObject<HTMLElement | null>): void {
14
14
  return;
15
15
  }
16
16
 
17
- const elementTop = element.current.getBoundingClientRect().top;
17
+ const elementTop = elementRef.current.getBoundingClientRect().top;
18
18
  const footerOffset = footer.offsetTop - window.scrollY - elementTop;
19
19
  if (footerOffset > window.innerHeight) {
20
- element.current.style.maxHeight = `${window.innerHeight - elementTop}px`;
20
+ elementRef.current.style.maxHeight = `${window.innerHeight - elementTop}px`;
21
21
  } else {
22
- element.current.style.maxHeight = `${footerOffset}px`;
22
+ elementRef.current.style.maxHeight = `${footerOffset}px`;
23
23
  }
24
- }, [element]);
24
+ }, [elementRef]);
25
25
 
26
26
  const throttledHandler = useMemo(() => throttle(handler, 150), [handler]);
27
27
 
28
28
  useEffect(() => {
29
- if (typeof window === 'undefined' || !element.current) {
29
+ if (typeof window === 'undefined' || !elementRef.current) {
30
30
  return () => {
31
31
  // nope
32
32
  };
@@ -45,5 +45,5 @@ export function useFullHeight(element: RefObject<HTMLElement | null>): void {
45
45
  window.removeEventListener('scroll', throttledHandler);
46
46
  window.removeEventListener('resize', throttledHandler);
47
47
  };
48
- }, [throttledHandler, element, handler]);
48
+ }, [throttledHandler, elementRef, handler]);
49
49
  }
@@ -1330,83 +1330,88 @@ const replay = css`
1330
1330
  `;
1331
1331
 
1332
1332
 
1333
+ /** The light theme tokens without a selector, for a host that sets them on one element. */
1334
+ export const tokens = css`
1335
+ ${accordion}
1336
+ ${admonition}
1337
+ ${apiReferenceDocs}
1338
+ ${apiReferencePanels}
1339
+ ${badges}
1340
+ ${banner}
1341
+ ${borders}
1342
+ ${consent}
1343
+ ${breadcrumbs}
1344
+ ${button}
1345
+ ${aiAssistantButton}
1346
+ ${cards}
1347
+ ${catalog}
1348
+ ${catalogClassic}
1349
+ ${code}
1350
+ ${codeWalkthrough}
1351
+ ${docsDropdown}
1352
+ ${dropdown}
1353
+ ${error}
1354
+ ${filter}
1355
+ ${footer}
1356
+ ${headingsTypography}
1357
+ ${httpTag}
1358
+ ${image}
1359
+ ${inputs}
1360
+ ${languagePicker}
1361
+ ${lastUpdated}
1362
+ ${links}
1363
+ ${loadProgressBar}
1364
+ ${logo}
1365
+ ${markdown}
1366
+ ${markdownAccordion}
1367
+ ${markdownTabs}
1368
+ ${markdownNumberedList}
1369
+ ${diagram}
1370
+ ${menu}
1371
+ ${mobileMenu}
1372
+ ${modal}
1373
+ ${navbar}
1374
+ ${pages}
1375
+ ${productPicker}
1376
+ ${responsePanelColors}
1377
+ ${search}
1378
+ ${navigationTabs}
1379
+ ${select}
1380
+ ${sidebar}
1381
+ ${sizeAndSpace}
1382
+ ${tag}
1383
+ ${themeColors}
1384
+ ${activeBrandPaletteLight}
1385
+ ${tile}
1386
+ ${toc}
1387
+ ${tooltip}
1388
+ ${typography}
1389
+ ${userMenu}
1390
+ ${versionPicker}
1391
+ ${zIndexDepth}
1392
+ ${scorecardColors}
1393
+ ${statusCode}
1394
+ ${tab}
1395
+ ${icon}
1396
+ ${tree}
1397
+ ${segmented}
1398
+ ${switcher}
1399
+ ${checkbox}
1400
+ ${feedback}
1401
+ ${scorecard}
1402
+ ${datePicker}
1403
+ ${replay}
1404
+ ${skipContent}
1405
+ ${pageActions}
1406
+ ${svgViewer}
1407
+ ${toast}
1408
+ ${pageNavigation}
1409
+
1410
+ `;
1411
+
1333
1412
  export const styles = css<{ palette?: string }>`
1334
1413
  :root {
1335
- ${accordion}
1336
- ${admonition}
1337
- ${apiReferenceDocs}
1338
- ${apiReferencePanels}
1339
- ${badges}
1340
- ${banner}
1341
- ${borders}
1342
- ${consent}
1343
- ${breadcrumbs}
1344
- ${button}
1345
- ${aiAssistantButton}
1346
- ${cards}
1347
- ${catalog}
1348
- ${catalogClassic}
1349
- ${code}
1350
- ${codeWalkthrough}
1351
- ${docsDropdown}
1352
- ${dropdown}
1353
- ${error}
1354
- ${filter}
1355
- ${footer}
1356
- ${headingsTypography}
1357
- ${httpTag}
1358
- ${image}
1359
- ${inputs}
1360
- ${languagePicker}
1361
- ${lastUpdated}
1362
- ${links}
1363
- ${loadProgressBar}
1364
- ${logo}
1365
- ${markdown}
1366
- ${markdownAccordion}
1367
- ${markdownTabs}
1368
- ${markdownNumberedList}
1369
- ${diagram}
1370
- ${menu}
1371
- ${mobileMenu}
1372
- ${modal}
1373
- ${navbar}
1374
- ${pages}
1375
- ${productPicker}
1376
- ${responsePanelColors}
1377
- ${search}
1378
- ${navigationTabs}
1379
- ${select}
1380
- ${sidebar}
1381
- ${sizeAndSpace}
1382
- ${tag}
1383
- ${themeColors}
1384
- ${activeBrandPaletteLight}
1385
- ${tile}
1386
- ${toc}
1387
- ${tooltip}
1388
- ${typography}
1389
- ${userMenu}
1390
- ${versionPicker}
1391
- ${zIndexDepth}
1392
- ${scorecardColors}
1393
- ${statusCode}
1394
- ${tab}
1395
- ${icon}
1396
- ${tree}
1397
- ${segmented}
1398
- ${switcher}
1399
- ${checkbox}
1400
- ${feedback}
1401
- ${scorecard}
1402
- ${datePicker}
1403
- ${replay}
1404
- ${skipContent}
1405
- ${pageActions}
1406
- ${svgViewer}
1407
- ${toast}
1408
- ${pageNavigation}
1409
-
1414
+ ${tokens}
1410
1415
  background-color: var(--bg-color);
1411
1416
  color: var(--text-color-primary);
1412
1417
  font-family: var(--font-family-base);
@@ -1,3 +1,4 @@
1
1
  export * from './global';
2
2
  export * from './dark';
3
- export { xyflow } from './xyflow';
3
+ export { xyflow } from './xyflow';
4
+ export { searchAiAppearAnimation } from './search-ai-appear';
@@ -0,0 +1,16 @@
1
+ import { css, keyframes } from 'styled-components';
2
+
3
+ const appear = keyframes`
4
+ from {
5
+ opacity: 0;
6
+ transform: translateY(4px);
7
+ }
8
+ `;
9
+
10
+ export const searchAiAppearAnimation = css`
11
+ animation: ${appear} var(--search-ai-activity-fade-duration) ease-out;
12
+
13
+ @media (prefers-reduced-motion: reduce) {
14
+ animation: none;
15
+ }
16
+ `;
@@ -0,0 +1 @@
1
+ export type DropdownAlignment = 'start' | 'end';
@@ -25,3 +25,4 @@ export type * from './tooltip';
25
25
  export type * from './toast';
26
26
  export type * from './link';
27
27
  export type * from './button-group';
28
+ export type * from './dropdown';
@@ -113,7 +113,7 @@ export type TranslationKey =
113
113
  | 'search.ai.feedback.thanks'
114
114
  | 'search.ai.toolCall.executed'
115
115
  | 'search.ai.toolCall.executing'
116
- | 'search.ai.toolCall.withArgs'
116
+ | 'search.ai.toolCall.usedTools'
117
117
  | 'search.ai.button'
118
118
  | 'search.ai.label'
119
119
  | 'search.ai.disclaimer'
@@ -124,6 +124,9 @@ export type TranslationKey =
124
124
  | 'search.ai.error.header.forbidden'
125
125
  | 'search.ai.error.header.unauthorized'
126
126
  | 'search.ai.feedback.more'
127
+ | 'search.ai.actions.copy'
128
+ | 'search.ai.actions.like'
129
+ | 'search.ai.actions.dislike'
127
130
  | 'search.searchItem.deprecated'
128
131
  | 'search.searchItem.enum'
129
132
  | 'search.searchItem.example'
@@ -1,6 +1,8 @@
1
+ import type { MCP_OPTIONS } from '../constants/mcp';
2
+
1
3
  export type MCPClientType = 'cursor' | 'vscode';
2
4
 
3
- export type MCPOption = 'cursor' | 'vscode' | 'claude-code' | 'codex' | 'copy';
5
+ export type MCPOption = (typeof MCP_OPTIONS)[number];
4
6
 
5
7
  export type McpConnectionParams = {
6
8
  serverName: string;
@@ -117,6 +117,27 @@ export type ToolCall = {
117
117
 
118
118
  export type ContentSegment = { type: 'text'; text: string } | { type: 'tool'; toolCall: ToolCall };
119
119
 
120
+ export type GroupedContentSegment =
121
+ | { type: 'text'; text: string }
122
+ /** `isActive`: the assistant still works on this group, with nothing written after it yet. */
123
+ | { type: 'tools'; toolCalls: ToolCall[]; isActive: boolean };
124
+
125
+ export type SearchAiActivityKind = 'thinking' | 'search' | 'code' | 'support';
126
+
127
+ export type SearchAiActivityStatus = {
128
+ label: string;
129
+ detail?: string;
130
+ kind: SearchAiActivityKind;
131
+ isRunning: boolean;
132
+ };
133
+
134
+ export type SearchAiActivityStep = {
135
+ id: string;
136
+ label: string;
137
+ kind: SearchAiActivityKind;
138
+ isRunning: boolean;
139
+ };
140
+
120
141
  export type AiSearchConversationItem = {
121
142
  role: AiSearchConversationRole;
122
143
  content: string;
@@ -0,0 +1,9 @@
1
+ import type { Ref } from 'react';
2
+
3
+ export function assignRef<T>(ref: Ref<T> | undefined, value: T): void {
4
+ if (typeof ref === 'function') {
5
+ ref(value);
6
+ } else if (ref) {
7
+ ref.current = value;
8
+ }
9
+ }