@redocly/theme 0.50.0 → 0.51.0-next.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 (39) hide show
  1. package/lib/components/PageNavigation/NextButton.js +1 -2
  2. package/lib/components/PageNavigation/PreviousButton.js +2 -2
  3. package/lib/components/Search/SearchAiResponse.d.ts +1 -0
  4. package/lib/components/Search/SearchAiResponse.js +35 -14
  5. package/lib/components/Search/SearchDialog.js +10 -7
  6. package/lib/core/hooks/code-walkthrough/use-code-panel.js +4 -3
  7. package/lib/core/hooks/code-walkthrough/use-code-walkthrough-controls.js +5 -0
  8. package/lib/core/hooks/use-color-switcher.d.ts +1 -1
  9. package/lib/core/hooks/use-color-switcher.js +10 -7
  10. package/lib/core/hooks/use-theme-hooks.js +1 -0
  11. package/lib/core/types/api-functions.d.ts +5 -0
  12. package/lib/core/types/api-functions.js +3 -0
  13. package/lib/core/types/hooks.d.ts +7 -1
  14. package/lib/core/types/index.d.ts +1 -0
  15. package/lib/core/types/index.js +1 -0
  16. package/lib/core/types/l10n.d.ts +1 -1
  17. package/lib/icons/ErrorFilledIcon/ErrorFilledIcon.d.ts +9 -0
  18. package/lib/icons/ErrorFilledIcon/ErrorFilledIcon.js +22 -0
  19. package/lib/index.d.ts +1 -0
  20. package/lib/index.js +1 -0
  21. package/lib/markdoc/components/CodeWalkthrough/CodeStep.js +3 -2
  22. package/lib/markdoc/components/CodeWalkthrough/CodeWalkthrough.js +1 -2
  23. package/package.json +1 -1
  24. package/src/components/PageNavigation/NextButton.tsx +2 -2
  25. package/src/components/PageNavigation/PreviousButton.tsx +3 -2
  26. package/src/components/Search/SearchAiResponse.tsx +69 -37
  27. package/src/components/Search/SearchDialog.tsx +10 -6
  28. package/src/core/hooks/code-walkthrough/use-code-panel.ts +4 -3
  29. package/src/core/hooks/code-walkthrough/use-code-walkthrough-controls.ts +5 -0
  30. package/src/core/hooks/use-color-switcher.ts +12 -7
  31. package/src/core/hooks/use-theme-hooks.ts +1 -0
  32. package/src/core/types/api-functions.ts +5 -0
  33. package/src/core/types/hooks.ts +11 -2
  34. package/src/core/types/index.ts +1 -0
  35. package/src/core/types/l10n.ts +2 -0
  36. package/src/icons/ErrorFilledIcon/ErrorFilledIcon.tsx +33 -0
  37. package/src/index.ts +1 -0
  38. package/src/markdoc/components/CodeWalkthrough/CodeStep.tsx +3 -1
  39. package/src/markdoc/components/CodeWalkthrough/CodeWalkthrough.tsx +1 -2
@@ -41,9 +41,8 @@ function NextButton({ nextPage, className }) {
41
41
  return React.createElement("div", null, "\u00A0");
42
42
  }
43
43
  const nextPageText = nextPage.label || nextPage.routeSlug || '';
44
- const defaultLabel = ((_b = navigation === null || navigation === void 0 ? void 0 : navigation.nextButton) === null || _b === void 0 ? void 0 : _b.text) || 'Next page';
45
44
  const translationKey = 'page.nextButton';
46
- const label = translate(translationKey, { defaultValue: defaultLabel });
45
+ const label = ((_b = navigation === null || navigation === void 0 ? void 0 : navigation.nextButton) === null || _b === void 0 ? void 0 : _b.text) || translate(translationKey, { defaultValue: 'Next page' });
47
46
  return (React.createElement(NextButtonWrapper, { "data-component-name": "PageNavigation/NextButton", "data-translation-key": translationKey },
48
47
  React.createElement(NextPageLabel, null, label),
49
48
  React.createElement(NextPageButton, { size: "large", to: nextPage.link, extraClass: className, variant: "link", icon: React.createElement(ArrowRightIcon_1.ArrowRightIcon, null), iconPosition: "right" }, nextPageText)));
@@ -18,9 +18,9 @@ function PreviousButton({ prevPage, className }) {
18
18
  return react_1.default.createElement("div", null, "\u00A0");
19
19
  }
20
20
  const prevPageText = prevPage.label || prevPage.routeSlug || '';
21
- const defaultLabel = ((_b = navigation === null || navigation === void 0 ? void 0 : navigation.previousButton) === null || _b === void 0 ? void 0 : _b.text) || 'Previous page';
22
21
  const translationKey = 'page.previousButton';
23
- const label = translate(translationKey, { defaultValue: defaultLabel });
22
+ const label = ((_b = navigation === null || navigation === void 0 ? void 0 : navigation.previousButton) === null || _b === void 0 ? void 0 : _b.text) ||
23
+ translate(translationKey, { defaultValue: 'Previous page' });
24
24
  return (react_1.default.createElement(PreviousButtonWrapper, { "data-component-name": "PageNavigation/PreviousButton", "data-translation-key": translationKey },
25
25
  react_1.default.createElement(PreviousPageLabel, null, label),
26
26
  react_1.default.createElement(PreviousPageButton, { size: "large", to: prevPage.link, extraClass: className, variant: "link", icon: react_1.default.createElement(ArrowLeftIcon_1.ArrowLeftIcon, null), iconPosition: "left" }, prevPageText)));
@@ -2,6 +2,7 @@ export type SearchAiResponseProps = {
2
2
  question: string;
3
3
  isGeneratingResponse: boolean;
4
4
  response?: string;
5
+ error: string | null;
5
6
  resources: {
6
7
  url: string;
7
8
  title: string;
@@ -13,25 +13,46 @@ const Tag_1 = require("../../components/Tag/Tag");
13
13
  const Link_1 = require("../../components/Link/Link");
14
14
  const hooks_1 = require("../../core/hooks");
15
15
  const Markdown_1 = require("../../components/Markdown/Markdown");
16
+ const Admonition_1 = require("../../components/Admonition/Admonition");
17
+ const ErrorFilledIcon_1 = require("../../icons/ErrorFilledIcon/ErrorFilledIcon");
16
18
  function SearchAiResponse(props) {
17
19
  const { useMarkdownText } = (0, hooks_1.useThemeHooks)();
18
- const { question, response, isGeneratingResponse, resources } = props;
20
+ const { question, response, isGeneratingResponse, resources, error } = props;
19
21
  const { useTranslate } = (0, hooks_1.useThemeHooks)();
20
22
  const { translate } = useTranslate();
21
23
  const markdownResponse = useMarkdownText(response || '');
22
- return (react_1.default.createElement(ResponseWrapper, { "data-component-name": "Search/SearchAiResponse" },
23
- react_1.default.createElement(ResponseHeader, null,
24
- isGeneratingResponse ? (react_1.default.createElement(Spinner_1.Spinner, { size: "20px", color: "--search-ai-spinner-icon-color" })) : (react_1.default.createElement(CheckmarkFilledIcon_1.CheckmarkFilledIcon, { size: "20px", color: "--search-ai-checkmark-icon-color" })),
25
- react_1.default.createElement(Question, null, question)),
26
- react_1.default.createElement(ResponseBody, null,
27
- response ? (react_1.default.createElement(ResponseText, Object.assign({ children: markdownResponse, as: "div" }, props))) : (react_1.default.createElement(ThinkingResponseText, { "data-translation-key": "search.ai.thinkingText" }, translate('search.ai.thinkingText', 'Thinking...'))),
28
- resources.length && !isGeneratingResponse ? (react_1.default.createElement(Resources, null,
29
- react_1.default.createElement(ResourcesTitle, { "data-translation-key": "search.ai.resourcesFound" },
30
- resources.length,
31
- " ",
32
- translate('search.ai.resourcesFound', 'resources found')),
33
- react_1.default.createElement(ResourceTags, null, resources.map((resource, idx) => (react_1.default.createElement(Link_1.Link, { key: idx, to: resource.url, target: "_blank" },
34
- react_1.default.createElement(ResourceTag, { borderless: true, icon: react_1.default.createElement(DocumentIcon_1.DocumentIcon, { color: "--search-ai-resource-tag-icon-color" }) }, resource.title))))))) : null)));
24
+ let responseContainer = null;
25
+ const hasPendingOrReceivedResponse = response || isGeneratingResponse || error;
26
+ if (hasPendingOrReceivedResponse) {
27
+ let icon;
28
+ switch (true) {
29
+ case error !== null:
30
+ icon = react_1.default.createElement(ErrorFilledIcon_1.ErrorFilledIcon, { size: "20px", color: "--error-bubble-content-color" });
31
+ break;
32
+ case isGeneratingResponse:
33
+ icon = react_1.default.createElement(Spinner_1.Spinner, { size: "20px", color: "--search-ai-spinner-icon-color" });
34
+ break;
35
+ case Boolean(response):
36
+ icon = react_1.default.createElement(CheckmarkFilledIcon_1.CheckmarkFilledIcon, { size: "20px", color: "--search-ai-checkmark-icon-color" });
37
+ break;
38
+ }
39
+ responseContainer = (react_1.default.createElement(react_1.default.Fragment, null,
40
+ react_1.default.createElement(ResponseHeader, null,
41
+ icon,
42
+ react_1.default.createElement(Question, null, question)),
43
+ react_1.default.createElement(ResponseBody, null,
44
+ response && react_1.default.createElement(ResponseText, Object.assign({ children: markdownResponse, as: "div" }, props)),
45
+ !response && isGeneratingResponse && (react_1.default.createElement(ThinkingResponseText, { "data-translation-key": "search.ai.thinkingText" }, translate('search.ai.thinkingText', 'Thinking...'))),
46
+ error && (react_1.default.createElement(Admonition_1.Admonition, { type: "danger", name: translate('search.ai.error.header', 'Oops! Something went wrong.') }, translate('search.ai.error.description', 'We encountered an issue while processing your search. Please try again later or refine your query. If the problem persists, feel free to contact support.'))),
47
+ resources.length && !isGeneratingResponse ? (react_1.default.createElement(Resources, null,
48
+ react_1.default.createElement(ResourcesTitle, { "data-translation-key": "search.ai.resourcesFound" },
49
+ resources.length,
50
+ " ",
51
+ translate('search.ai.resourcesFound', 'resources found')),
52
+ react_1.default.createElement(ResourceTags, null, resources.map((resource, idx) => (react_1.default.createElement(Link_1.Link, { key: idx, to: resource.url, target: "_blank" },
53
+ react_1.default.createElement(ResourceTag, { borderless: true, icon: react_1.default.createElement(DocumentIcon_1.DocumentIcon, { color: "--search-ai-resource-tag-icon-color" }) }, resource.title))))))) : null)));
54
+ }
55
+ return (react_1.default.createElement(ResponseWrapper, { "data-component-name": "Search/SearchAiResponse" }, responseContainer));
35
56
  }
36
57
  const ResponseWrapper = styled_components_1.default.div `
37
58
  display: flex;
@@ -46,8 +46,8 @@ const SpinnerLoader_1 = require("../../components/Loaders/SpinnerLoader");
46
46
  const SettingsIcon_1 = require("../../icons/SettingsIcon/SettingsIcon");
47
47
  const AiStarsIcon_1 = require("../../icons/AiStarsIcon/AiStarsIcon");
48
48
  function SearchDialog({ onClose, className }) {
49
- const { useTranslate, useCurrentProduct, useSearch, useProducts, useAiSearch, useTelemetry } = (0, hooks_1.useThemeHooks)();
50
- const telemetry = useTelemetry();
49
+ const { useTranslate, useCurrentProduct, useSearch, useProducts, useAiSearch, useOtelTelemetry } = (0, hooks_1.useThemeHooks)();
50
+ const otelTelemetry = useOtelTelemetry();
51
51
  const products = useProducts();
52
52
  const currentProduct = useCurrentProduct();
53
53
  const [product, setProduct] = (0, react_1.useState)(currentProduct);
@@ -79,11 +79,11 @@ function SearchDialog({ onClose, className }) {
79
79
  : undefined;
80
80
  }
81
81
  return (react_1.default.createElement(SearchItem_1.SearchItem, { key: `${index}-${item.document.id}`, item: item, product: itemProduct, onClick: () => {
82
- telemetry.send('search_result_clicked', {
82
+ otelTelemetry.send('search.result.clicked', {
83
83
  query,
84
84
  url: item.document.url,
85
- total_results: results.length,
86
- index,
85
+ total_results: results.length.toString(),
86
+ index: index.toString(),
87
87
  search_engine: mode,
88
88
  });
89
89
  } }));
@@ -116,7 +116,10 @@ function SearchDialog({ onClose, className }) {
116
116
  react_1.default.createElement(CloseIcon_1.CloseIcon, { onClick: () => setProduct(undefined), color: "--icon-color-additional" })))),
117
117
  react_1.default.createElement(SearchInput_1.SearchInput, { value: query, onChange: setQuery, placeholder: mode === 'search'
118
118
  ? translate('search.label', 'Search docs...')
119
- : translate('search.ai.label', 'Ask AI assistant'), isLoading: isSearchLoading, showReturnButton: mode === 'ai-dialog', onReturn: () => setMode('search'), onSubmit: mode === 'ai-dialog'
119
+ : translate('search.ai.label', 'Ask AI assistant'), isLoading: isSearchLoading, showReturnButton: mode === 'ai-dialog', onReturn: () => {
120
+ setMode('search');
121
+ aiSearch.clearAiSearchState();
122
+ }, onSubmit: mode === 'ai-dialog'
120
123
  ? () => {
121
124
  setQuery('');
122
125
  aiSearch.askQuestion(query);
@@ -147,7 +150,7 @@ function SearchDialog({ onClose, className }) {
147
150
  react_1.default.createElement(SearchRecent_1.SearchRecent, { onSelect: setQuery }),
148
151
  react_1.default.createElement(SearchSuggestedPages_1.SearchSuggestedPages, null)))),
149
152
  advancedSearch && mode === 'search' && isFilterOpen && (react_1.default.createElement(SearchDialogBodyFilterView, null,
150
- react_1.default.createElement(SearchFilter_1.SearchFilter, { facets: facets, filter: filter, query: query, quickFilterFields: [groupField], onFilterChange: onFilterChange, onFilterReset: onFilterReset, onFacetReset: onFacetReset }))))) : (react_1.default.createElement(SearchAiResponse_1.SearchAiResponse, { question: aiSearch.question, isGeneratingResponse: aiSearch.isGeneratingResponse, response: aiSearch.response, resources: aiSearch.resources }))),
153
+ react_1.default.createElement(SearchFilter_1.SearchFilter, { facets: facets, filter: filter, query: query, quickFilterFields: [groupField], onFilterChange: onFilterChange, onFilterReset: onFilterReset, onFacetReset: onFacetReset }))))) : (react_1.default.createElement(SearchAiResponse_1.SearchAiResponse, { question: aiSearch.question, isGeneratingResponse: aiSearch.isGeneratingResponse, response: aiSearch.response, resources: aiSearch.resources, error: aiSearch.error }))),
151
154
  react_1.default.createElement(SearchDialogFooter, null, mode === 'ai-dialog' ? (react_1.default.createElement(AiDisclaimer, null, translate('search.ai.disclaimer', 'AI search might provide incomplete or incorrect results. Verify important information.'))) : (react_1.default.createElement(react_1.default.Fragment, null,
152
155
  react_1.default.createElement(SearchShortcuts, null,
153
156
  react_1.default.createElement(SearchShortcut_1.SearchShortcut, { "data-translation-key": "search.keys.navigate", combination: "Tab", text: translate('search.keys.navigate', 'to navigate') }),
@@ -82,9 +82,10 @@ function getCodeLinesFromNode(node, activeStep, areConditionsMet, populateInputs
82
82
  }
83
83
  else {
84
84
  const shouldRenderChunk = areConditionsMet(node.condition);
85
- const isHighlighted = activeStep != null &&
86
- node.condition.steps.length > 0 &&
87
- node.condition.steps.includes(activeStep);
85
+ const isHighlighted = parentHighlighted ||
86
+ (activeStep != null &&
87
+ node.condition.steps.length > 0 &&
88
+ node.condition.steps.includes(activeStep));
88
89
  return shouldRenderChunk
89
90
  ? node.children.flatMap((child) => getCodeLinesFromNode(child, activeStep, areConditionsMet, populateInputsWithValue, isHighlighted))
90
91
  : [];
@@ -75,6 +75,11 @@ function useCodeWalkthroughControls(filters, inputs, toggles, enableDeepLink) {
75
75
  };
76
76
  const walkthroughContext = (0, react_1.useMemo)(() => {
77
77
  const areConditionsMet = (conditions) => (0, utils_1.matchCodeWalkthroughConditions)(conditions, controlsState);
78
+ // reset controls
79
+ for (const control of Object.values(controlsState)) {
80
+ control.render = true;
81
+ control.value = control.value || defaultControlsValues[control.type];
82
+ }
78
83
  for (const [id, control] of Object.entries(controlsState)) {
79
84
  if (control && !areConditionsMet(control)) {
80
85
  controlsState[id].render = false;
@@ -1,6 +1,6 @@
1
1
  export declare const useColorSwitcher: () => {
2
2
  isSwitcherHidden: boolean | undefined;
3
3
  initActiveColorMode: () => void;
4
- switchColorMode: () => string;
4
+ switchColorMode: (mode?: string) => void;
5
5
  activeColorMode: string;
6
6
  };
@@ -15,17 +15,20 @@ const useColorSwitcher = () => {
15
15
  const activeMode = Array.from(document.documentElement.classList).find((c) => modes.includes(c));
16
16
  setActiveColorMode(activeMode || defaultColor);
17
17
  };
18
- const switchColorMode = () => {
18
+ const switchColorMode = (mode) => {
19
+ if (mode && !modes.includes(mode)) {
20
+ return;
21
+ }
19
22
  const activeIndex = modes.indexOf(activeColorMode);
20
- const mode = activeIndex < modes.length - 1 ? modes[activeIndex + 1] : modes[0];
21
- localStorage.setItem('colorSchema', mode);
22
- document.documentElement.className = `${mode} notransition`;
23
+ // If specific mode is provided, use it, otherwise cycle through modes
24
+ const newMode = mode || (activeIndex < modes.length - 1 ? modes[activeIndex + 1] : modes[0]);
25
+ localStorage.setItem('colorSchema', newMode);
26
+ document.documentElement.className = `${newMode} notransition`;
23
27
  window.requestAnimationFrame(() => {
24
28
  document.documentElement.classList.remove('notransition');
25
29
  });
26
- telemetry.send('color_mode_switched', { from: activeColorMode, to: mode });
27
- setActiveColorMode(mode);
28
- return mode;
30
+ telemetry.send('color_mode_switched', { from: activeColorMode, to: newMode });
31
+ setActiveColorMode(newMode);
29
32
  };
30
33
  return {
31
34
  isSwitcherHidden: colorMode === null || colorMode === void 0 ? void 0 : colorMode.hide,
@@ -9,6 +9,7 @@ const fallbacks = {
9
9
  }),
10
10
  useSubmitFeedback: () => ({ submitFeedback: () => { } }),
11
11
  useTelemetry: () => ({ telemetry: () => { } }),
12
+ useOtelTelemetry: () => ({ send: () => { } }),
12
13
  useBreadcrumbs: () => [],
13
14
  useCodeHighlight: () => ({ highlight: (rawContent) => rawContent }),
14
15
  useUserMenu: () => ({}),
@@ -0,0 +1,5 @@
1
+ export type { ApiFunctionsRequest as Request } from '@redocly/config';
2
+ export type { ApiFunctionsResponse as Response } from '@redocly/config';
3
+ export type { ApiFunctionsContext as Context } from '@redocly/config';
4
+ export type { PropsContext } from '@redocly/config';
5
+ export type { PageStaticData as PropsData } from '@redocly/config';
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=api-functions.js.map
@@ -73,6 +73,8 @@ export type ThemeHooks = {
73
73
  title: string;
74
74
  url: string;
75
75
  }[];
76
+ error: null | string;
77
+ clearAiSearchState: () => void;
76
78
  };
77
79
  useMarkdownText: (text: string) => React.ReactNode;
78
80
  useFacetQuery: (field: string) => {
@@ -98,6 +100,9 @@ export type ThemeHooks = {
98
100
  useTelemetry: () => {
99
101
  send(action: TelemetryEvent, data: unknown): void;
100
102
  };
103
+ useOtelTelemetry: () => {
104
+ send(action: OtelTelemetryEvent, data: unknown): void;
105
+ };
101
106
  useUserTeams: () => string[];
102
107
  usePageProps: <T extends Record<string, unknown>>() => PageProps & T;
103
108
  useCodeHighlight: () => {
@@ -121,5 +126,6 @@ export type L10nConfig = {
121
126
  name: string;
122
127
  }[];
123
128
  };
124
- type TelemetryEvent = 'search_opened' | 'client_error_caught' | 'breadcrumb_clicked' | 'color_mode_switched' | 'sidebar_item_clicked' | 'sidebar_item_expanded' | 'sidebar_item_collapsed' | 'edit_page_link_clicked' | 'code_snippet_copied' | 'code_snippet_reported' | 'feedback_sent' | 'navbar_menu_item_clicked' | 'login_button_clicked' | 'login_provider_button_clicked' | 'logout_menu_item_clicked' | 'logo_clicked' | 'toc_item_clicked' | 'version_switched' | 'catalog_filter_changed' | 'catalog_item_clicked' | 'scorecard_link_clicked' | 'markdown_anchor_link_clicked' | 'back_to_catalog_button_clicked' | 'sidebar_drilldown_back_button_clicked' | 'footer_item_clicked' | 'request_api_access_button_clicked' | 'sidebar_samples_button_clicked' | 'change_layout_button_clicked' | 'version_picker_selection_change' | 'openapi_docs' | 'replay' | 'catalog_actions_button_clicked' | 'catalog_item_clicked' | 'filter_checkbox_toggled' | 'language_picker_locale_changed' | 'mobile_menu_button_close_clicked' | 'mobile_menu_button_open_clicked' | 'search_input_reset_button_clicked' | 'search_recent_remove_button_clicked' | 'product_picked' | 'search_result_clicked';
129
+ type TelemetryEvent = 'search_opened' | 'client_error_caught' | 'breadcrumb_clicked' | 'color_mode_switched' | 'sidebar_item_clicked' | 'sidebar_item_expanded' | 'sidebar_item_collapsed' | 'edit_page_link_clicked' | 'code_snippet_copied' | 'code_snippet_reported' | 'feedback_sent' | 'navbar_menu_item_clicked' | 'login_button_clicked' | 'login_provider_button_clicked' | 'logout_menu_item_clicked' | 'logo_clicked' | 'toc_item_clicked' | 'version_switched' | 'catalog_filter_changed' | 'catalog_item_clicked' | 'scorecard_link_clicked' | 'markdown_anchor_link_clicked' | 'back_to_catalog_button_clicked' | 'sidebar_drilldown_back_button_clicked' | 'footer_item_clicked' | 'request_api_access_button_clicked' | 'sidebar_samples_button_clicked' | 'change_layout_button_clicked' | 'version_picker_selection_change' | 'openapi_docs' | 'replay' | 'catalog_actions_button_clicked' | 'catalog_item_clicked' | 'filter_checkbox_toggled' | 'language_picker_locale_changed' | 'mobile_menu_button_close_clicked' | 'mobile_menu_button_open_clicked' | 'search_input_reset_button_clicked' | 'search_recent_remove_button_clicked' | 'product_picked';
130
+ type OtelTelemetryEvent = 'page.viewed' | 'error' | 'search.ai.query' | 'search.query' | 'search.result.clicked';
125
131
  export {};
@@ -10,3 +10,4 @@ export * from '../../core/types/select';
10
10
  export * from '../../core/types/sidebar';
11
11
  export * from '../../core/types/filter';
12
12
  export * from '../../core/types/user-menu';
13
+ export * from '../../core/types/api-functions';
@@ -26,4 +26,5 @@ __exportStar(require("../../core/types/select"), exports);
26
26
  __exportStar(require("../../core/types/sidebar"), exports);
27
27
  __exportStar(require("../../core/types/filter"), exports);
28
28
  __exportStar(require("../../core/types/user-menu"), exports);
29
+ __exportStar(require("../../core/types/api-functions"), exports);
29
30
  //# sourceMappingURL=index.js.map
@@ -1,5 +1,5 @@
1
1
  import type { TOptions } from 'i18next';
2
- export type TranslationKey = 'dev.newApp' | 'dev.newApp.text' | 'dev.sidebar.header' | 'dev.sidebar.footer.text' | 'dev.create.app.dialog.appName.placeholder' | 'dev.create.app.dialog.appName.error' | 'dev.create.app.dialog.selectAPIs' | 'dev.create.app.dialog.description' | 'dev.create.app.dialog.description.placeholder' | 'dev.create.app.dialog.create' | 'dev.create.app.dialog.cancel' | 'dev.main.tab.appKeys' | 'dev.main.tab.logs' | 'dev.app.description.title' | 'dev.edit.description.dialog.title' | 'dev.edit.description.dialog.save' | 'dev.edit.description.dialog.cancel' | 'dev.edit.apis.dialog.selectedAPIs' | 'dev.app.key.create' | 'dev.create.key.dialog.title' | 'dev.create.key.dialog.create' | 'dev.create.key.dialog.cancel' | 'dev.app.edit' | 'dev.app.delete' | 'dev.edit.app.dialog.title' | 'dev.edit.app.dialog.save' | 'dev.edit.app.dialog.cancel' | 'dev.delete.app.dialog.title' | 'dev.delete.app.dialog.confirmation' | 'dev.delete.app.dialog.delete' | 'dev.delete.app.dialog.cancel' | 'dev.app.key.roll' | 'dev.roll.key.dialog.title' | 'dev.roll.key.dialog.apiKey' | 'dev.roll.key.dialog.expires' | 'dev.roll.key.dialog.confirmation' | 'dev.roll.key.dialog.cancel' | 'dev.roll.key.dialog.roll' | 'dev.update.key.dialog.title' | 'dev.update.key.dialog.update' | 'dev.update.key.dialog.cancel' | 'dev.app.key.api.name' | 'dev.app.key.api.status' | 'dev.app.key.api.edit' | 'dev.edit.apis.dialog.title' | 'dev.edit.apis.dialog.apiKey' | 'dev.edit.apis.dialog.save' | 'dev.edit.apis.dialog.cancel' | 'dev.select.placeholder' | 'dev.app.overview.status.pending' | 'dev.app.overview.status.approved' | 'dev.app.overview.status.revoked' | 'dev.app.overview.status' | 'dev.app.overview.non-production' | 'dev.app.overview.production' | 'dev.app.overview.clientId' | 'dev.app.overview.apiKey' | 'dev.app.key.revoke' | 'dev.revoke.key.dialog.title' | 'dev.revoke.key.dialog.apiKey' | 'dev.revoke.key.dialog.expires' | 'dev.revoke.key.dialog.confirmation' | 'dev.revoke.key.dialog.revoke' | 'dev.revoke.key.dialog.cancel' | 'dev.app.overview.expires' | 'dev.app.overview.created' | 'dev.app.overview.visibilityToggle.hide' | 'dev.app.overview.visibilityToggle.show' | 'search.loading' | 'search.noResults.title' | 'search.keys.navigate' | 'search.keys.select' | 'search.keys.exit' | 'search.label' | 'search.cancel' | 'search.recent' | 'search.navbar.label' | 'search.suggested' | 'search.showMore' | 'search.filter.title' | 'search.filter.reset' | 'search.filter.field.reset' | 'search.ai.thinkingText' | 'search.ai.resourcesFound' | 'search.ai.button' | 'search.ai.label' | 'search.ai.disclaimer' | 'toc.header' | 'footer.copyrightText' | 'page.homeButton' | 'page.forbidden.title' | 'page.notFound.title' | 'page.notFound.description' | 'page.lastUpdated.timeago' | 'page.lastUpdated.on' | 'catalog.filters.placeholder' | 'catalog.filters.title' | 'catalog.filters.clearAll' | 'catalog.filters.select.addFilter' | 'catalog.filters.select.all' | 'catalog.filters.done' | 'sidebar.menu.backLabel' | 'sidebar.menu.backToLabel' | 'sidebar.actions.show' | 'sidebar.actions.hide' | 'sidebar.actions.changeLayout' | 'versionPicker.label' | 'versionPicker.unversioned' | 'codeSnippet.copy.buttonText' | 'codeSnippet.copy.tooltipText' | 'codeSnippet.copy.toasterText' | 'markdown.editPage.text' | 'feedback.settings.comment.submitText' | 'feedback.settings.comment.label' | 'feedback.settings.comment.send' | 'feedback.settings.comment.cancel' | 'feedback.settings.comment.satisfiedLabel' | 'feedback.settings.comment.neutralLabel' | 'feedback.settings.comment.dissatisfiedLabel' | 'feedback.settings.submitText' | 'feedback.settings.label' | 'feedback.settings.reasons.label' | 'feedback.settings.reasons.send' | 'feedback.settings.comment.likeLabel' | 'feedback.settings.comment.dislikeLabel' | 'feedback.sentiment.thumbUp' | 'feedback.sentiment.thumbDown' | 'feedback.settings.leftScaleLabel' | 'feedback.settings.rightScaleLabel' | 'feedback.settings.optionalEmail.placeholder' | 'feedback.settings.optionalEmail.label' | 'codeSnippet.report.buttonText' | 'codeSnippet.report.tooltipText' | 'codeSnippet.report.label' | 'userMenu.login' | 'userMenu.logout' | 'userMenu.devOnboardingLabel' | 'mobileMenu.mainMenu' | 'mobileMenu.previous' | 'mobileMenu.products' | 'page.nextButton' | 'page.previousButton' | 'openapi.download.description.title' | 'openapi.info.title' | 'openapi.info.contact.url' | 'openapi.info.contact.name' | 'openapi.info.license' | 'openapi.info.termsOfService' | 'openapi.info.metadata.title' | 'openapi.key' | 'openapi.value' | 'openapi.enum' | 'openapi.items' | 'openapi.default' | 'openapi.variable' | 'openapi.variables' | 'openapi.actions.show' | 'openapi.actions.hide' | 'openapi.actions.more' | 'openapi.languages.title' | 'openapi.servers.title' | 'openapi.operations' | 'openapi.webhooks' | 'openapi.description' | 'openapi.badges.deprecated' | 'openapi.badges.required' | 'openapi.badges.webhook' | 'openapi.request' | 'openapi.path' | 'openapi.query' | 'openapi.cookie' | 'openapi.header' | 'openapi.body' | 'openapi.responses' | 'openapi.response' | 'openapi.callbacks' | 'openapi.callbackRequest' | 'openapi.callbackResponse' | 'openapi.payload' | 'openapi.discriminator' | 'openapi.contentType' | 'openapi.tryIt' | 'openapi.loading' | 'openapi.example' | 'openapi.examples' | 'openapi.additionalProperties' | 'openapi.patternProperties' | 'openapi.required' | 'openapi.recursive' | 'openapi.complex' | 'openapi.deprecated' | 'openapi.hideExample' | 'openapi.showExample' | 'openapi.expandAll' | 'openapi.collapseAll' | 'openapi.noResponseExample' | 'openapi.noResponseContent' | 'openapi.noRequestPayload' | 'openapi.hidePattern' | 'openapi.showPattern' | 'openapi.authorizationUrl' | 'openapi.tokenUrl' | 'openapi.refreshUrl' | 'openapi.scopes' | 'openapi.security' | 'openapi.httpAuthorizationScheme' | 'openapi.bearerFormat' | 'openapi.parameterName' | 'openapi.flowType' | 'openapi.connectUrl' | 'openapi.requiredScopes' | 'openapi.unsupportedLanguage' | 'openapi.failedToGenerateCodeSample' | 'graphql.queries' | 'graphql.mutations' | 'graphql.subscriptions' | 'graphql.directives' | 'graphql.objects' | 'graphql.interfaces' | 'graphql.unions' | 'graphql.enums' | 'graphql.inputs' | 'graphql.scalars' | 'graphql.arguments.label' | 'graphql.arguments.show' | 'graphql.arguments.hide' | 'graphql.arguments.here' | 'graphql.returnTypes.label' | 'graphql.returnTypes.show' | 'graphql.returnTypes.hide' | 'graphql.possibleTypes' | 'graphql.defaultValue' | 'graphql.deprecationReason' | 'graphql.implementedInterfaces' | 'graphql.nonNull' | 'graphql.required' | 'graphql.deprecated' | 'graphql.variables' | 'graphql.querySample' | 'graphql.mutationSample' | 'graphql.subscriptionSample' | 'graphql.responseSample' | 'graphql.locations' | 'graphql.sample' | 'graphql.referenced' | 'codeWalkthrough.download' | 'codeWalkthrough.preview';
2
+ export type TranslationKey = 'dev.newApp' | 'dev.newApp.text' | 'dev.sidebar.header' | 'dev.sidebar.footer.text' | 'dev.create.app.dialog.appName.placeholder' | 'dev.create.app.dialog.appName.error' | 'dev.create.app.dialog.selectAPIs' | 'dev.create.app.dialog.description' | 'dev.create.app.dialog.description.placeholder' | 'dev.create.app.dialog.create' | 'dev.create.app.dialog.cancel' | 'dev.main.tab.appKeys' | 'dev.main.tab.logs' | 'dev.app.description.title' | 'dev.edit.description.dialog.title' | 'dev.edit.description.dialog.save' | 'dev.edit.description.dialog.cancel' | 'dev.edit.apis.dialog.selectedAPIs' | 'dev.app.key.create' | 'dev.create.key.dialog.title' | 'dev.create.key.dialog.create' | 'dev.create.key.dialog.cancel' | 'dev.app.edit' | 'dev.app.delete' | 'dev.edit.app.dialog.title' | 'dev.edit.app.dialog.save' | 'dev.edit.app.dialog.cancel' | 'dev.delete.app.dialog.title' | 'dev.delete.app.dialog.confirmation' | 'dev.delete.app.dialog.delete' | 'dev.delete.app.dialog.cancel' | 'dev.app.key.roll' | 'dev.roll.key.dialog.title' | 'dev.roll.key.dialog.apiKey' | 'dev.roll.key.dialog.expires' | 'dev.roll.key.dialog.confirmation' | 'dev.roll.key.dialog.cancel' | 'dev.roll.key.dialog.roll' | 'dev.update.key.dialog.title' | 'dev.update.key.dialog.update' | 'dev.update.key.dialog.cancel' | 'dev.app.key.api.name' | 'dev.app.key.api.status' | 'dev.app.key.api.edit' | 'dev.edit.apis.dialog.title' | 'dev.edit.apis.dialog.apiKey' | 'dev.edit.apis.dialog.save' | 'dev.edit.apis.dialog.cancel' | 'dev.select.placeholder' | 'dev.app.overview.status.pending' | 'dev.app.overview.status.approved' | 'dev.app.overview.status.revoked' | 'dev.app.overview.status' | 'dev.app.overview.non-production' | 'dev.app.overview.production' | 'dev.app.overview.clientId' | 'dev.app.overview.apiKey' | 'dev.app.key.revoke' | 'dev.revoke.key.dialog.title' | 'dev.revoke.key.dialog.apiKey' | 'dev.revoke.key.dialog.expires' | 'dev.revoke.key.dialog.confirmation' | 'dev.revoke.key.dialog.revoke' | 'dev.revoke.key.dialog.cancel' | 'dev.app.overview.expires' | 'dev.app.overview.created' | 'dev.app.overview.visibilityToggle.hide' | 'dev.app.overview.visibilityToggle.show' | 'search.loading' | 'search.noResults.title' | 'search.keys.navigate' | 'search.keys.select' | 'search.keys.exit' | 'search.label' | 'search.cancel' | 'search.recent' | 'search.navbar.label' | 'search.suggested' | 'search.showMore' | 'search.filter.title' | 'search.filter.reset' | 'search.filter.field.reset' | 'search.ai.thinkingText' | 'search.ai.resourcesFound' | 'search.ai.button' | 'search.ai.label' | 'search.ai.disclaimer' | 'search.ai.error.description' | 'search.ai.error.header' | 'toc.header' | 'footer.copyrightText' | 'page.homeButton' | 'page.forbidden.title' | 'page.notFound.title' | 'page.notFound.description' | 'page.lastUpdated.timeago' | 'page.lastUpdated.on' | 'catalog.filters.placeholder' | 'catalog.filters.title' | 'catalog.filters.clearAll' | 'catalog.filters.select.addFilter' | 'catalog.filters.select.all' | 'catalog.filters.done' | 'sidebar.menu.backLabel' | 'sidebar.menu.backToLabel' | 'sidebar.actions.show' | 'sidebar.actions.hide' | 'sidebar.actions.changeLayout' | 'versionPicker.label' | 'versionPicker.unversioned' | 'codeSnippet.copy.buttonText' | 'codeSnippet.copy.tooltipText' | 'codeSnippet.copy.toasterText' | 'markdown.editPage.text' | 'feedback.settings.comment.submitText' | 'feedback.settings.comment.label' | 'feedback.settings.comment.send' | 'feedback.settings.comment.cancel' | 'feedback.settings.comment.satisfiedLabel' | 'feedback.settings.comment.neutralLabel' | 'feedback.settings.comment.dissatisfiedLabel' | 'feedback.settings.submitText' | 'feedback.settings.label' | 'feedback.settings.reasons.label' | 'feedback.settings.reasons.send' | 'feedback.settings.comment.likeLabel' | 'feedback.settings.comment.dislikeLabel' | 'feedback.sentiment.thumbUp' | 'feedback.sentiment.thumbDown' | 'feedback.settings.leftScaleLabel' | 'feedback.settings.rightScaleLabel' | 'feedback.settings.optionalEmail.placeholder' | 'feedback.settings.optionalEmail.label' | 'codeSnippet.report.buttonText' | 'codeSnippet.report.tooltipText' | 'codeSnippet.report.label' | 'userMenu.login' | 'userMenu.logout' | 'userMenu.devOnboardingLabel' | 'mobileMenu.mainMenu' | 'mobileMenu.previous' | 'mobileMenu.products' | 'page.nextButton' | 'page.previousButton' | 'openapi.download.description.title' | 'openapi.info.title' | 'openapi.info.contact.url' | 'openapi.info.contact.name' | 'openapi.info.license' | 'openapi.info.termsOfService' | 'openapi.info.metadata.title' | 'openapi.key' | 'openapi.value' | 'openapi.enum' | 'openapi.items' | 'openapi.default' | 'openapi.variable' | 'openapi.variables' | 'openapi.actions.show' | 'openapi.actions.hide' | 'openapi.actions.more' | 'openapi.languages.title' | 'openapi.servers.title' | 'openapi.operations' | 'openapi.webhooks' | 'openapi.description' | 'openapi.badges.deprecated' | 'openapi.badges.required' | 'openapi.badges.webhook' | 'openapi.request' | 'openapi.path' | 'openapi.query' | 'openapi.cookie' | 'openapi.header' | 'openapi.body' | 'openapi.responses' | 'openapi.response' | 'openapi.callbacks' | 'openapi.callbackRequest' | 'openapi.callbackResponse' | 'openapi.payload' | 'openapi.discriminator' | 'openapi.contentType' | 'openapi.tryIt' | 'openapi.loading' | 'openapi.example' | 'openapi.examples' | 'openapi.additionalProperties' | 'openapi.patternProperties' | 'openapi.required' | 'openapi.recursive' | 'openapi.complex' | 'openapi.deprecated' | 'openapi.hideExample' | 'openapi.showExample' | 'openapi.expandAll' | 'openapi.collapseAll' | 'openapi.noResponseExample' | 'openapi.noResponseContent' | 'openapi.noRequestPayload' | 'openapi.hidePattern' | 'openapi.showPattern' | 'openapi.authorizationUrl' | 'openapi.tokenUrl' | 'openapi.refreshUrl' | 'openapi.scopes' | 'openapi.security' | 'openapi.httpAuthorizationScheme' | 'openapi.bearerFormat' | 'openapi.parameterName' | 'openapi.flowType' | 'openapi.connectUrl' | 'openapi.requiredScopes' | 'openapi.unsupportedLanguage' | 'openapi.failedToGenerateCodeSample' | 'graphql.queries' | 'graphql.mutations' | 'graphql.subscriptions' | 'graphql.directives' | 'graphql.objects' | 'graphql.interfaces' | 'graphql.unions' | 'graphql.enums' | 'graphql.inputs' | 'graphql.scalars' | 'graphql.arguments.label' | 'graphql.arguments.show' | 'graphql.arguments.hide' | 'graphql.arguments.here' | 'graphql.returnTypes.label' | 'graphql.returnTypes.show' | 'graphql.returnTypes.hide' | 'graphql.possibleTypes' | 'graphql.defaultValue' | 'graphql.deprecationReason' | 'graphql.implementedInterfaces' | 'graphql.nonNull' | 'graphql.required' | 'graphql.deprecated' | 'graphql.variables' | 'graphql.querySample' | 'graphql.mutationSample' | 'graphql.subscriptionSample' | 'graphql.responseSample' | 'graphql.locations' | 'graphql.sample' | 'graphql.referenced' | 'codeWalkthrough.download' | 'codeWalkthrough.preview';
3
3
  export type Locale = {
4
4
  code: string;
5
5
  name: string;
@@ -0,0 +1,9 @@
1
+ import React from 'react';
2
+ import type { IconProps } from '../../icons/types';
3
+ export declare const ErrorFilledIcon: import("styled-components").StyledComponent<(props: IconProps) => React.JSX.Element, any, {
4
+ 'data-component-name': string;
5
+ } & {
6
+ color?: string;
7
+ size?: string;
8
+ className?: string;
9
+ } & React.SVGProps<SVGSVGElement>, "data-component-name">;
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.ErrorFilledIcon = void 0;
7
+ const react_1 = __importDefault(require("react"));
8
+ const styled_components_1 = __importDefault(require("styled-components"));
9
+ const utils_1 = require("../../core/utils");
10
+ const Icon = (props) => (react_1.default.createElement("svg", Object.assign({ xmlns: "http://www.w3.org/2000/svg", width: "20", height: "20", viewBox: "0 0 20 20", fill: "none" }, props),
11
+ react_1.default.createElement("path", { d: "M10.0002 1.24998C8.84911 1.24285 7.70808 1.4643 6.64326 1.9015C5.57844 2.3387 4.61101 2.98295 3.79707 3.79689C2.98313 4.61083 2.33889 5.57826 1.90168 6.64308C1.46448 7.7079 1.24303 8.84892 1.25017 9.99998C1.24303 11.151 1.46448 12.2921 1.90168 13.3569C2.33889 14.4217 2.98313 15.3891 3.79707 16.2031C4.61101 17.017 5.57844 17.6613 6.64326 18.0985C7.70808 18.5357 8.84911 18.7571 10.0002 18.75C11.1512 18.7571 12.2923 18.5357 13.3571 18.0985C14.4219 17.6613 15.3893 17.017 16.2033 16.2031C17.0172 15.3891 17.6614 14.4217 18.0986 13.3569C18.5359 12.2921 18.7573 11.151 18.7502 9.99998C18.7573 8.84892 18.5359 7.7079 18.0986 6.64308C17.6614 5.57826 17.0172 4.61083 16.2033 3.79689C15.3893 2.98295 14.4219 2.3387 13.3571 1.9015C12.2923 1.4643 11.1512 1.24285 10.0002 1.24998ZM13.4032 14.375L5.62517 6.5973L6.59748 5.62498L14.3752 13.403L13.4032 14.375Z", fill: "#F9316D" })));
12
+ exports.ErrorFilledIcon = (0, styled_components_1.default)(Icon).attrs(() => ({
13
+ 'data-component-name': 'icons/ErrorFilledIcon/ErrorFilledIcon',
14
+ })) `
15
+ path {
16
+ fill: ${({ color }) => (0, utils_1.getCssColorVariable)(color)};
17
+ }
18
+
19
+ height: ${({ size }) => size || '16px'};
20
+ width: ${({ size }) => size || '16px'};
21
+ `;
22
+ //# sourceMappingURL=ErrorFilledIcon.js.map
package/lib/index.d.ts CHANGED
@@ -212,6 +212,7 @@ export * from './icons/CharacterIcon/CharacterIcon';
212
212
  export * from './icons/FileIcon/FileIcon';
213
213
  export * from './icons/ExportIcon/ExportIcon';
214
214
  export * from './icons/CertificateIcon/CertificateIcon';
215
+ export * from './icons/ErrorFilledIcon/ErrorFilledIcon';
215
216
  export * from './layouts/RootLayout';
216
217
  export * from './layouts/PageLayout';
217
218
  export * from './layouts/NotFound';
package/lib/index.js CHANGED
@@ -264,6 +264,7 @@ __exportStar(require("./icons/CharacterIcon/CharacterIcon"), exports);
264
264
  __exportStar(require("./icons/FileIcon/FileIcon"), exports);
265
265
  __exportStar(require("./icons/ExportIcon/ExportIcon"), exports);
266
266
  __exportStar(require("./icons/CertificateIcon/CertificateIcon"), exports);
267
+ __exportStar(require("./icons/ErrorFilledIcon/ErrorFilledIcon"), exports);
267
268
  /* Layouts */
268
269
  __exportStar(require("./layouts/RootLayout"), exports);
269
270
  __exportStar(require("./layouts/PageLayout"), exports);
@@ -53,7 +53,8 @@ function CodeStep({ id, heading, stepKey, when, unless, children, }) {
53
53
  // If the step is active during first render, scroll to it
54
54
  // This is to ensure that the step is visible when the page is loaded
55
55
  if (isActive) {
56
- handleActivateStep();
56
+ // Ensure scrollMarginTop calculated before step being scrolled.
57
+ setTimeout(handleActivateStep, 5);
57
58
  }
58
59
  // Ignore dependency array because we only need to run this once
59
60
  // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -84,7 +85,7 @@ function CodeStep({ id, heading, stepKey, when, unless, children, }) {
84
85
  }
85
86
  return null;
86
87
  }
87
- return (react_1.default.createElement(exports.StepWrapper, { "data-component-name": "Markdoc/CodeWalkthrough/CodeStep", ref: compRef, isActive: isActive, scrollMarginTop: scrollMarginTop, "data-step-key": stepKey, "data-step-active": isActive, onClick: handleActivateStep, onFocus: handleActivateStep, tabIndex: 0 },
88
+ return (react_1.default.createElement(exports.StepWrapper, { "data-component-name": "Markdoc/CodeWalkthrough/CodeStep", ref: compRef, isActive: isActive, scrollMarginTop: scrollMarginTop, "data-step-key": stepKey, "data-step-active": isActive, onClick: handleActivateStep, onFocus: handleActivateStep, tabIndex: 0, className: "code-step-wrapper" },
88
89
  react_1.default.createElement(StepContent, { isActive: isActive },
89
90
  heading ? react_1.default.createElement(StepHeading, null, heading) : null,
90
91
  children)));
@@ -44,7 +44,6 @@ const contexts_1 = require("../../../core/contexts");
44
44
  const CodePanel_1 = require("../../../markdoc/components/CodeWalkthrough/CodePanel");
45
45
  const hooks_1 = require("../../../core/hooks");
46
46
  const CodeFilters_1 = require("../../../markdoc/components/CodeWalkthrough/CodeFilters");
47
- const CodeStep_1 = require("../../../markdoc/components/CodeWalkthrough/CodeStep");
48
47
  function CodeWalkthrough(_a) {
49
48
  var { children, steps, preview } = _a, attributes = __rest(_a, ["children", "steps", "preview"]);
50
49
  // Steps are being modified causing the observer to be recreated in the useCodeWalkthrough hook
@@ -95,7 +94,7 @@ const ContentWrapper = styled_components_1.default.div `
95
94
  min-height: 0;
96
95
  max-width: var(--md-content-max-width);
97
96
 
98
- & > *:not(${CodeStep_1.StepWrapper}) {
97
+ & > *:not(.code-step-wrapper) {
99
98
  padding-left: var(--spacing-xl);
100
99
  }
101
100
  overflow-y: scroll;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@redocly/theme",
3
- "version": "0.50.0",
3
+ "version": "0.51.0-next.0",
4
4
  "description": "Shared UI components lib",
5
5
  "keywords": [
6
6
  "theme",
@@ -22,9 +22,9 @@ export function NextButton({ nextPage, className }: NextPageType): JSX.Element {
22
22
  }
23
23
 
24
24
  const nextPageText = nextPage.label || nextPage.routeSlug || '';
25
- const defaultLabel = navigation?.nextButton?.text || 'Next page';
26
25
  const translationKey = 'page.nextButton';
27
- const label = translate(translationKey, { defaultValue: defaultLabel });
26
+ const label =
27
+ navigation?.nextButton?.text || translate(translationKey, { defaultValue: 'Next page' });
28
28
 
29
29
  return (
30
30
  <NextButtonWrapper
@@ -22,9 +22,10 @@ export function PreviousButton({ prevPage, className }: PreviousPageType): JSX.E
22
22
  }
23
23
 
24
24
  const prevPageText = prevPage.label || prevPage.routeSlug || '';
25
- const defaultLabel = navigation?.previousButton?.text || 'Previous page';
26
25
  const translationKey = 'page.previousButton';
27
- const label = translate(translationKey, { defaultValue: defaultLabel });
26
+ const label =
27
+ navigation?.previousButton?.text ||
28
+ translate(translationKey, { defaultValue: 'Previous page' });
28
29
 
29
30
  return (
30
31
  <PreviousButtonWrapper
@@ -8,11 +8,14 @@ import { Tag } from '@redocly/theme/components/Tag/Tag';
8
8
  import { Link } from '@redocly/theme/components/Link/Link';
9
9
  import { useThemeHooks } from '@redocly/theme/core/hooks';
10
10
  import { Markdown } from '@redocly/theme/components/Markdown/Markdown';
11
+ import { Admonition } from '@redocly/theme/components/Admonition/Admonition';
12
+ import { ErrorFilledIcon } from '@redocly/theme/icons/ErrorFilledIcon/ErrorFilledIcon';
11
13
 
12
14
  export type SearchAiResponseProps = {
13
15
  question: string;
14
16
  isGeneratingResponse: boolean;
15
17
  response?: string;
18
+ error: string | null;
16
19
  resources: {
17
20
  url: string;
18
21
  title: string;
@@ -21,50 +24,79 @@ export type SearchAiResponseProps = {
21
24
 
22
25
  export function SearchAiResponse(props: SearchAiResponseProps): JSX.Element {
23
26
  const { useMarkdownText } = useThemeHooks();
24
- const { question, response, isGeneratingResponse, resources } = props;
27
+ const { question, response, isGeneratingResponse, resources, error } = props;
25
28
 
26
29
  const { useTranslate } = useThemeHooks();
27
30
  const { translate } = useTranslate();
28
31
  const markdownResponse = useMarkdownText(response || '');
29
32
 
33
+ let responseContainer = null;
34
+
35
+ const hasPendingOrReceivedResponse = response || isGeneratingResponse || error;
36
+ if (hasPendingOrReceivedResponse) {
37
+ let icon;
38
+ switch (true) {
39
+ case error !== null:
40
+ icon = <ErrorFilledIcon size="20px" color="--error-bubble-content-color" />;
41
+ break;
42
+ case isGeneratingResponse:
43
+ icon = <Spinner size="20px" color="--search-ai-spinner-icon-color" />;
44
+ break;
45
+ case Boolean(response):
46
+ icon = <CheckmarkFilledIcon size="20px" color="--search-ai-checkmark-icon-color" />;
47
+ break;
48
+ }
49
+ responseContainer = (
50
+ <>
51
+ <ResponseHeader>
52
+ {icon}
53
+ <Question>{question}</Question>
54
+ </ResponseHeader>
55
+ <ResponseBody>
56
+ {response && <ResponseText children={markdownResponse} as="div" {...props} />}
57
+ {!response && isGeneratingResponse && (
58
+ <ThinkingResponseText data-translation-key="search.ai.thinkingText">
59
+ {translate('search.ai.thinkingText', 'Thinking...')}
60
+ </ThinkingResponseText>
61
+ )}
62
+ {error && (
63
+ <Admonition
64
+ type="danger"
65
+ name={translate('search.ai.error.header', 'Oops! Something went wrong.')}
66
+ >
67
+ {translate(
68
+ 'search.ai.error.description',
69
+ 'We encountered an issue while processing your search. Please try again later or refine your query. If the problem persists, feel free to contact support.',
70
+ )}
71
+ </Admonition>
72
+ )}
73
+ {resources.length && !isGeneratingResponse ? (
74
+ <Resources>
75
+ <ResourcesTitle data-translation-key="search.ai.resourcesFound">
76
+ {resources.length} {translate('search.ai.resourcesFound', 'resources found')}
77
+ </ResourcesTitle>
78
+ <ResourceTags>
79
+ {resources.map((resource, idx) => (
80
+ <Link key={idx} to={resource.url} target="_blank">
81
+ <ResourceTag
82
+ borderless
83
+ icon={<DocumentIcon color="--search-ai-resource-tag-icon-color" />}
84
+ >
85
+ {resource.title}
86
+ </ResourceTag>
87
+ </Link>
88
+ ))}
89
+ </ResourceTags>
90
+ </Resources>
91
+ ) : null}
92
+ </ResponseBody>
93
+ </>
94
+ );
95
+ }
96
+
30
97
  return (
31
98
  <ResponseWrapper data-component-name="Search/SearchAiResponse">
32
- <ResponseHeader>
33
- {isGeneratingResponse ? (
34
- <Spinner size="20px" color="--search-ai-spinner-icon-color" />
35
- ) : (
36
- <CheckmarkFilledIcon size="20px" color="--search-ai-checkmark-icon-color" />
37
- )}
38
- <Question>{question}</Question>
39
- </ResponseHeader>
40
- <ResponseBody>
41
- {response ? (
42
- <ResponseText children={markdownResponse} as="div" {...props} />
43
- ) : (
44
- <ThinkingResponseText data-translation-key="search.ai.thinkingText">
45
- {translate('search.ai.thinkingText', 'Thinking...')}
46
- </ThinkingResponseText>
47
- )}
48
- {resources.length && !isGeneratingResponse ? (
49
- <Resources>
50
- <ResourcesTitle data-translation-key="search.ai.resourcesFound">
51
- {resources.length} {translate('search.ai.resourcesFound', 'resources found')}
52
- </ResourcesTitle>
53
- <ResourceTags>
54
- {resources.map((resource, idx) => (
55
- <Link key={idx} to={resource.url} target="_blank">
56
- <ResourceTag
57
- borderless
58
- icon={<DocumentIcon color="--search-ai-resource-tag-icon-color" />}
59
- >
60
- {resource.title}
61
- </ResourceTag>
62
- </Link>
63
- ))}
64
- </ResourceTags>
65
- </Resources>
66
- ) : null}
67
- </ResponseBody>
99
+ {responseContainer}
68
100
  </ResponseWrapper>
69
101
  );
70
102
  }
@@ -27,9 +27,9 @@ export type SearchDialogProps = {
27
27
  };
28
28
 
29
29
  export function SearchDialog({ onClose, className }: SearchDialogProps): JSX.Element {
30
- const { useTranslate, useCurrentProduct, useSearch, useProducts, useAiSearch, useTelemetry } =
30
+ const { useTranslate, useCurrentProduct, useSearch, useProducts, useAiSearch, useOtelTelemetry } =
31
31
  useThemeHooks();
32
- const telemetry = useTelemetry();
32
+ const otelTelemetry = useOtelTelemetry();
33
33
  const products = useProducts();
34
34
  const currentProduct = useCurrentProduct();
35
35
  const [product, setProduct] = useState(currentProduct);
@@ -88,11 +88,11 @@ export function SearchDialog({ onClose, className }: SearchDialogProps): JSX.Ele
88
88
  item={item}
89
89
  product={itemProduct}
90
90
  onClick={() => {
91
- telemetry.send('search_result_clicked', {
91
+ otelTelemetry.send('search.result.clicked', {
92
92
  query,
93
93
  url: item.document.url,
94
- total_results: results.length,
95
- index,
94
+ total_results: results.length.toString(),
95
+ index: index.toString(),
96
96
  search_engine: mode,
97
97
  });
98
98
  }}
@@ -146,7 +146,10 @@ export function SearchDialog({ onClose, className }: SearchDialogProps): JSX.Ele
146
146
  }
147
147
  isLoading={isSearchLoading}
148
148
  showReturnButton={mode === 'ai-dialog'}
149
- onReturn={() => setMode('search')}
149
+ onReturn={() => {
150
+ setMode('search');
151
+ aiSearch.clearAiSearchState();
152
+ }}
150
153
  onSubmit={
151
154
  mode === 'ai-dialog'
152
155
  ? () => {
@@ -250,6 +253,7 @@ export function SearchDialog({ onClose, className }: SearchDialogProps): JSX.Ele
250
253
  isGeneratingResponse={aiSearch.isGeneratingResponse}
251
254
  response={aiSearch.response}
252
255
  resources={aiSearch.resources}
256
+ error={aiSearch.error}
253
257
  />
254
258
  )}
255
259
  </SearchDialogBody>
@@ -145,9 +145,10 @@ function getCodeLinesFromNode(
145
145
  const shouldRenderChunk = areConditionsMet(node.condition);
146
146
 
147
147
  const isHighlighted =
148
- activeStep != null &&
149
- node.condition.steps.length > 0 &&
150
- node.condition.steps.includes(activeStep);
148
+ parentHighlighted ||
149
+ (activeStep != null &&
150
+ node.condition.steps.length > 0 &&
151
+ node.condition.steps.includes(activeStep));
151
152
 
152
153
  return shouldRenderChunk
153
154
  ? node.children.flatMap((child) =>
@@ -155,6 +155,11 @@ export function useCodeWalkthroughControls(
155
155
  const walkthroughContext = useMemo(() => {
156
156
  const areConditionsMet = (conditions: CodeWalkthroughConditionsObject) =>
157
157
  matchCodeWalkthroughConditions(conditions, controlsState);
158
+ // reset controls
159
+ for (const control of Object.values(controlsState)) {
160
+ control.render = true;
161
+ control.value = control.value || defaultControlsValues[control.type];
162
+ }
158
163
 
159
164
  for (const [id, control] of Object.entries(controlsState)) {
160
165
  if (control && !areConditionsMet(control)) {
@@ -18,19 +18,24 @@ export const useColorSwitcher = () => {
18
18
  setActiveColorMode(activeMode || defaultColor);
19
19
  };
20
20
 
21
- const switchColorMode = (): string => {
21
+ const switchColorMode = (mode?: string): void => {
22
+ if (mode && !modes.includes(mode)) {
23
+ return;
24
+ }
25
+
22
26
  const activeIndex = modes.indexOf(activeColorMode);
23
- const mode = activeIndex < modes.length - 1 ? modes[activeIndex + 1] : modes[0];
24
- localStorage.setItem('colorSchema', mode);
25
- document.documentElement.className = `${mode} notransition`;
27
+ // If specific mode is provided, use it, otherwise cycle through modes
28
+ const newMode = mode || (activeIndex < modes.length - 1 ? modes[activeIndex + 1] : modes[0]);
29
+
30
+ localStorage.setItem('colorSchema', newMode);
31
+ document.documentElement.className = `${newMode} notransition`;
26
32
 
27
33
  window.requestAnimationFrame(() => {
28
34
  document.documentElement.classList.remove('notransition');
29
35
  });
30
- telemetry.send('color_mode_switched', { from: activeColorMode, to: mode });
36
+ telemetry.send('color_mode_switched', { from: activeColorMode, to: newMode });
31
37
 
32
- setActiveColorMode(mode);
33
- return mode;
38
+ setActiveColorMode(newMode);
34
39
  };
35
40
 
36
41
  return {
@@ -11,6 +11,7 @@ const fallbacks = {
11
11
  }),
12
12
  useSubmitFeedback: () => ({ submitFeedback: () => {} }),
13
13
  useTelemetry: () => ({ telemetry: () => {} }),
14
+ useOtelTelemetry: () => ({ send: () => {} }),
14
15
  useBreadcrumbs: () => [],
15
16
  useCodeHighlight: () => ({ highlight: (rawContent: string) => rawContent }),
16
17
  useUserMenu: () => ({}),
@@ -0,0 +1,5 @@
1
+ export type { ApiFunctionsRequest as Request } from '@redocly/config';
2
+ export type { ApiFunctionsResponse as Response } from '@redocly/config';
3
+ export type { ApiFunctionsContext as Context } from '@redocly/config';
4
+ export type { PropsContext } from '@redocly/config';
5
+ export type { PageStaticData as PropsData } from '@redocly/config';
@@ -88,6 +88,8 @@ export type ThemeHooks = {
88
88
  title: string;
89
89
  url: string;
90
90
  }[];
91
+ error: null | string;
92
+ clearAiSearchState: () => void;
91
93
  };
92
94
  useMarkdownText: (text: string) => React.ReactNode;
93
95
  useFacetQuery: (field: string) => {
@@ -110,6 +112,7 @@ export type ThemeHooks = {
110
112
  | undefined;
111
113
  useCatalog: (config: CatalogConfig) => FilteredCatalog;
112
114
  useTelemetry: () => { send(action: TelemetryEvent, data: unknown): void };
115
+ useOtelTelemetry: () => { send(action: OtelTelemetryEvent, data: unknown): void };
113
116
  useUserTeams: () => string[];
114
117
  usePageProps: <T extends Record<string, unknown>>() => PageProps & T;
115
118
  useCodeHighlight: () => {
@@ -173,5 +176,11 @@ type TelemetryEvent =
173
176
  | 'mobile_menu_button_open_clicked'
174
177
  | 'search_input_reset_button_clicked'
175
178
  | 'search_recent_remove_button_clicked'
176
- | 'product_picked'
177
- | 'search_result_clicked';
179
+ | 'product_picked';
180
+
181
+ type OtelTelemetryEvent =
182
+ | 'page.viewed'
183
+ | 'error'
184
+ | 'search.ai.query'
185
+ | 'search.query'
186
+ | 'search.result.clicked';
@@ -10,3 +10,4 @@ export * from '@redocly/theme/core/types/select';
10
10
  export * from '@redocly/theme/core/types/sidebar';
11
11
  export * from '@redocly/theme/core/types/filter';
12
12
  export * from '@redocly/theme/core/types/user-menu';
13
+ export * from '@redocly/theme/core/types/api-functions';
@@ -88,6 +88,8 @@ export type TranslationKey =
88
88
  | 'search.ai.button'
89
89
  | 'search.ai.label'
90
90
  | 'search.ai.disclaimer'
91
+ | 'search.ai.error.description'
92
+ | 'search.ai.error.header'
91
93
  | 'toc.header'
92
94
  | 'footer.copyrightText'
93
95
  | 'page.homeButton'
@@ -0,0 +1,33 @@
1
+ import React from 'react';
2
+ import styled from 'styled-components';
3
+
4
+ import type { IconProps } from '@redocly/theme/icons/types';
5
+
6
+ import { getCssColorVariable } from '@redocly/theme/core/utils';
7
+
8
+ const Icon = (props: IconProps) => (
9
+ <svg
10
+ xmlns="http://www.w3.org/2000/svg"
11
+ width="20"
12
+ height="20"
13
+ viewBox="0 0 20 20"
14
+ fill="none"
15
+ {...props}
16
+ >
17
+ <path
18
+ d="M10.0002 1.24998C8.84911 1.24285 7.70808 1.4643 6.64326 1.9015C5.57844 2.3387 4.61101 2.98295 3.79707 3.79689C2.98313 4.61083 2.33889 5.57826 1.90168 6.64308C1.46448 7.7079 1.24303 8.84892 1.25017 9.99998C1.24303 11.151 1.46448 12.2921 1.90168 13.3569C2.33889 14.4217 2.98313 15.3891 3.79707 16.2031C4.61101 17.017 5.57844 17.6613 6.64326 18.0985C7.70808 18.5357 8.84911 18.7571 10.0002 18.75C11.1512 18.7571 12.2923 18.5357 13.3571 18.0985C14.4219 17.6613 15.3893 17.017 16.2033 16.2031C17.0172 15.3891 17.6614 14.4217 18.0986 13.3569C18.5359 12.2921 18.7573 11.151 18.7502 9.99998C18.7573 8.84892 18.5359 7.7079 18.0986 6.64308C17.6614 5.57826 17.0172 4.61083 16.2033 3.79689C15.3893 2.98295 14.4219 2.3387 13.3571 1.9015C12.2923 1.4643 11.1512 1.24285 10.0002 1.24998ZM13.4032 14.375L5.62517 6.5973L6.59748 5.62498L14.3752 13.403L13.4032 14.375Z"
19
+ fill="#F9316D"
20
+ />
21
+ </svg>
22
+ );
23
+
24
+ export const ErrorFilledIcon = styled(Icon).attrs(() => ({
25
+ 'data-component-name': 'icons/ErrorFilledIcon/ErrorFilledIcon',
26
+ }))<IconProps>`
27
+ path {
28
+ fill: ${({ color }) => getCssColorVariable(color)};
29
+ }
30
+
31
+ height: ${({ size }) => size || '16px'};
32
+ width: ${({ size }) => size || '16px'};
33
+ `;
package/src/index.ts CHANGED
@@ -236,6 +236,7 @@ export * from '@redocly/theme/icons/CharacterIcon/CharacterIcon';
236
236
  export * from '@redocly/theme/icons/FileIcon/FileIcon';
237
237
  export * from '@redocly/theme/icons/ExportIcon/ExportIcon';
238
238
  export * from '@redocly/theme/icons/CertificateIcon/CertificateIcon';
239
+ export * from '@redocly/theme/icons/ErrorFilledIcon/ErrorFilledIcon';
239
240
  /* Layouts */
240
241
  export * from '@redocly/theme/layouts/RootLayout';
241
242
  export * from '@redocly/theme/layouts/PageLayout';
@@ -49,7 +49,8 @@ export function CodeStep({
49
49
  // If the step is active during first render, scroll to it
50
50
  // This is to ensure that the step is visible when the page is loaded
51
51
  if (isActive) {
52
- handleActivateStep();
52
+ // Ensure scrollMarginTop calculated before step being scrolled.
53
+ setTimeout(handleActivateStep, 5);
53
54
  }
54
55
 
55
56
  // Ignore dependency array because we only need to run this once
@@ -97,6 +98,7 @@ export function CodeStep({
97
98
  onClick={handleActivateStep}
98
99
  onFocus={handleActivateStep}
99
100
  tabIndex={0}
101
+ className="code-step-wrapper"
100
102
  >
101
103
  <StepContent isActive={isActive}>
102
104
  {heading ? <StepHeading>{heading}</StepHeading> : null}
@@ -13,7 +13,6 @@ import {
13
13
  CodeFilters,
14
14
  GetFilterState,
15
15
  } from '@redocly/theme/markdoc/components/CodeWalkthrough/CodeFilters';
16
- import { StepWrapper } from '@redocly/theme/markdoc/components/CodeWalkthrough/CodeStep';
17
16
 
18
17
  export type CodeWalkthroughProps = PropsWithChildren<CodeWalkthroughAttr>;
19
18
 
@@ -92,7 +91,7 @@ const ContentWrapper = styled.div`
92
91
  min-height: 0;
93
92
  max-width: var(--md-content-max-width);
94
93
 
95
- & > *:not(${StepWrapper}) {
94
+ & > *:not(.code-step-wrapper) {
96
95
  padding-left: var(--spacing-xl);
97
96
  }
98
97
  overflow-y: scroll;