@redocly/theme 0.70.0-next.6 → 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.
@@ -1,14 +1,46 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
5
35
  Object.defineProperty(exports, "__esModule", { value: true });
6
36
  exports.Portal = Portal;
7
- const react_1 = __importDefault(require("react"));
37
+ const react_1 = __importStar(require("react"));
8
38
  const react_dom_1 = require("react-dom");
9
39
  const utils_1 = require("../../core/utils");
40
+ const PortalContainerContext_1 = require("../../core/contexts/PortalContainerContext");
10
41
  function Portal({ children, mountId = 'root', }) {
11
- const mount = utils_1.IS_BROWSER ? document.getElementById(mountId) || document.body : null;
42
+ const container = (0, react_1.useContext)(PortalContainerContext_1.PortalContainerContext);
43
+ const mount = utils_1.IS_BROWSER ? container || document.getElementById(mountId) || document.body : null;
12
44
  if (!mount) {
13
45
  return react_1.default.createElement(react_1.default.Fragment, null, children);
14
46
  }
@@ -13,11 +13,16 @@ const ThumbUpFilledIcon_1 = require("../../icons/ThumbUpFilledIcon/ThumbUpFilled
13
13
  const ThumbDownIcon_1 = require("../../icons/ThumbDownIcon/ThumbDownIcon");
14
14
  const ThumbDownFilledIcon_1 = require("../../icons/ThumbDownFilledIcon/ThumbDownFilledIcon");
15
15
  const CopyButton_1 = require("../../components/Buttons/CopyButton");
16
+ const Tooltip_1 = require("../../components/Tooltip/Tooltip");
17
+ const hooks_1 = require("../../core/hooks");
16
18
  function SearchAiActionButtons({ content, className, feedback, onFeedback, disabled, }) {
19
+ const { useTranslate } = (0, hooks_1.useThemeHooks)();
20
+ const { translate } = useTranslate();
17
21
  return (react_1.default.createElement(ActionButtonsWrapper, { className: className, "data-component-name": "Search/SearchAiActionButtons" },
18
- react_1.default.createElement(CopyButton_1.CopyButton, { data: content }),
19
- react_1.default.createElement(FeedbackButton, { variant: "text", size: "small", icon: feedback === types_1.FeedbackType.Like ? react_1.default.createElement(ThumbUpFilledIcon_1.ThumbUpFilledIcon, null) : react_1.default.createElement(ThumbUpIcon_1.ThumbUpIcon, null), onClick: () => !disabled && onFeedback(types_1.FeedbackType.Like), extraClass: feedback === types_1.FeedbackType.Like ? 'active' : '', "aria-label": "Like this response", disabled: disabled }),
20
- react_1.default.createElement(FeedbackButton, { variant: "text", size: "small", icon: feedback === types_1.FeedbackType.Dislike ? react_1.default.createElement(ThumbDownFilledIcon_1.ThumbDownFilledIcon, null) : react_1.default.createElement(ThumbDownIcon_1.ThumbDownIcon, null), onClick: () => !disabled && onFeedback(types_1.FeedbackType.Dislike), extraClass: feedback === types_1.FeedbackType.Dislike ? 'active' : '', "aria-label": "Dislike this response", disabled: disabled })));
22
+ react_1.default.createElement(Tooltip_1.Tooltip, { tip: translate('search.ai.actions.copy', 'Copy the response'), placement: "top" },
23
+ react_1.default.createElement(CopyButton_1.CopyButton, { data: content })),
24
+ react_1.default.createElement(Tooltip_1.Tooltip, { tip: translate('search.ai.actions.like', 'Like the response'), placement: "top" }, react_1.default.createElement(FeedbackButton, { variant: "text", size: "small", icon: feedback === types_1.FeedbackType.Like ? react_1.default.createElement(ThumbUpFilledIcon_1.ThumbUpFilledIcon, null) : react_1.default.createElement(ThumbUpIcon_1.ThumbUpIcon, null), onClick: () => !disabled && onFeedback(types_1.FeedbackType.Like), extraClass: feedback === types_1.FeedbackType.Like ? 'active' : '', "aria-label": "Like this response", disabled: disabled })),
25
+ react_1.default.createElement(Tooltip_1.Tooltip, { tip: translate('search.ai.actions.dislike', 'Dislike the response'), placement: "top" }, react_1.default.createElement(FeedbackButton, { variant: "text", size: "small", icon: feedback === types_1.FeedbackType.Dislike ? react_1.default.createElement(ThumbDownFilledIcon_1.ThumbDownFilledIcon, null) : react_1.default.createElement(ThumbDownIcon_1.ThumbDownIcon, null), onClick: () => !disabled && onFeedback(types_1.FeedbackType.Dislike), extraClass: feedback === types_1.FeedbackType.Dislike ? 'active' : '', "aria-label": "Dislike this response", disabled: disabled }))));
21
26
  }
22
27
  const ActionButtonsWrapper = styled_components_1.default.div `
23
28
  display: flex;
@@ -76,6 +76,16 @@ function SearchDialog({ onClose, className, initialMode = 'search', }) {
76
76
  const autoSearchDisabled = mode !== 'search';
77
77
  const { query, setQuery, filter, setFilter, items, isSearchLoading, facets, groupCounts, setLoadMore, advancedSearch, askAi, groupField, searchError, retrySearch, } = useSearch(product === null || product === void 0 ? void 0 : product.name, autoSearchDisabled);
78
78
  const { isFilterOpen, onFilterToggle, onFilterChange, onFilterReset, onFacetReset, onQuickFilterReset, } = (0, hooks_1.useSearchFilter)(filter, setFilter);
79
+ (0, hooks_1.usePersistedQuickFilter)({
80
+ filter,
81
+ setFilter,
82
+ facets,
83
+ groupField,
84
+ query,
85
+ isSearchLoading,
86
+ searchError,
87
+ enabled: initialMode === 'search',
88
+ });
79
89
  const { addSearchHistoryItem } = (0, hooks_1.useRecentSearches)();
80
90
  const aiSearch = useAiSearch({ filter: filter, product: product === null || product === void 0 ? void 0 : product.name });
81
91
  (0, hooks_1.useModalScrollLock)(true);
@@ -0,0 +1,2 @@
1
+ /** Where theme portals mount; set it inside a shadow root (the AI assistant web component), else `document.body`. */
2
+ export declare const PortalContainerContext: import("react").Context<HTMLElement | null>;
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PortalContainerContext = void 0;
4
+ const react_1 = require("react");
5
+ /** Where theme portals mount; set it inside a shadow root (the AI assistant web component), else `document.body`. */
6
+ exports.PortalContainerContext = (0, react_1.createContext)(null);
7
+ //# sourceMappingURL=PortalContainerContext.js.map
@@ -31,6 +31,7 @@ export * from './search/use-search-dialog';
31
31
  export * from './search/use-search-filter';
32
32
  export * from './search/use-feedback-tooltip';
33
33
  export * from './search/use-search-ai-activity';
34
+ export * from './search/use-persisted-quick-filter';
34
35
  export * from './use-controlled-state';
35
36
  export * from './use-codeblock-tabs-controls';
36
37
  export * from './code-walkthrough/use-code-walkthrough';
@@ -47,6 +47,7 @@ __exportStar(require("./search/use-search-dialog"), exports);
47
47
  __exportStar(require("./search/use-search-filter"), exports);
48
48
  __exportStar(require("./search/use-feedback-tooltip"), exports);
49
49
  __exportStar(require("./search/use-search-ai-activity"), exports);
50
+ __exportStar(require("./search/use-persisted-quick-filter"), exports);
50
51
  __exportStar(require("./use-controlled-state"), exports);
51
52
  __exportStar(require("./use-codeblock-tabs-controls"), exports);
52
53
  __exportStar(require("./code-walkthrough/use-code-walkthrough"), exports);
@@ -0,0 +1,21 @@
1
+ import type { Dispatch, SetStateAction } from 'react';
2
+ import type { SearchFacet, SearchFilterItem } from '../../types/search';
3
+ type UsePersistedQuickFilterOptions = {
4
+ filter: SearchFilterItem[];
5
+ setFilter: Dispatch<SetStateAction<SearchFilterItem[]>>;
6
+ facets: SearchFacet[];
7
+ groupField: string;
8
+ query: string;
9
+ isSearchLoading: boolean;
10
+ searchError: string | null;
11
+ enabled?: boolean;
12
+ };
13
+ /**
14
+ * Remembers the quick filter on `groupField` for the browser tab and restores it on mount unless
15
+ * one is already selected. Restored values are dropped once known not to exist: absent from the
16
+ * full value list, or from the `groupField` facet left by the first search that finishes without
17
+ * error on an empty query (the restored value is that search's filter, so a value without
18
+ * documents is not counted). Does nothing while `enabled` is false.
19
+ */
20
+ export declare function usePersistedQuickFilter({ filter, setFilter, facets, groupField, query, isSearchLoading, searchError, enabled, }: UsePersistedQuickFilterOptions): void;
21
+ export {};
@@ -0,0 +1,92 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.usePersistedQuickFilter = usePersistedQuickFilter;
4
+ const react_1 = require("react");
5
+ const use_store_1 = require("../use-store");
6
+ const quickFilterStore = (0, use_store_1.createStore)({
7
+ storageKey: 'searchQuickFilter',
8
+ storageType: 'sessionStorage',
9
+ });
10
+ const findQuickFilter = (filter) => filter.find((item) => item.isQuickFilter);
11
+ const withQuickFilter = (filter, quickFilter) => [
12
+ ...filter.filter((item) => !item.isQuickFilter),
13
+ ...(quickFilter.values.length ? [Object.assign(Object.assign({}, quickFilter), { isQuickFilter: true })] : []),
14
+ ];
15
+ const groupFacetValues = (facets, field) => {
16
+ var _a;
17
+ const facet = facets.find((item) => item.field === field);
18
+ const values = (_a = facet === null || facet === void 0 ? void 0 : facet.values) !== null && _a !== void 0 ? _a : [];
19
+ return {
20
+ present: facet !== undefined,
21
+ values: values.map((item) => (typeof item === 'string' ? item : item.value)),
22
+ counted: values.some((item) => typeof item !== 'string' && item.isCounterVisible),
23
+ };
24
+ };
25
+ const isRestorable = (stored, groupField) => !!stored &&
26
+ stored.field === groupField &&
27
+ Array.isArray(stored.values) &&
28
+ stored.values.length > 0 &&
29
+ stored.values.every((value) => typeof value === 'string');
30
+ /**
31
+ * Remembers the quick filter on `groupField` for the browser tab and restores it on mount unless
32
+ * one is already selected. Restored values are dropped once known not to exist: absent from the
33
+ * full value list, or from the `groupField` facet left by the first search that finishes without
34
+ * error on an empty query (the restored value is that search's filter, so a value without
35
+ * documents is not counted). Does nothing while `enabled` is false.
36
+ */
37
+ function usePersistedQuickFilter({ filter, setFilter, facets, groupField, query, isSearchLoading, searchError, enabled = true, }) {
38
+ const [stored, setStored] = (0, use_store_1.useStore)(quickFilterStore, null);
39
+ const [initial] = (0, react_1.useState)(() => enabled && isRestorable(stored, groupField) && !findQuickFilter(filter) ? stored.values : null);
40
+ const phaseRef = (0, react_1.useRef)(initial ? 'restoring' : 'done');
41
+ const searchedRef = (0, react_1.useRef)(false);
42
+ (0, react_1.useEffect)(() => {
43
+ if (!initial)
44
+ return;
45
+ // 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
46
+ setFilter((current) => withQuickFilter(current, { field: groupField, values: initial }));
47
+ // eslint-disable-next-line react-hooks/exhaustive-deps -- applies only to the initial state
48
+ }, []);
49
+ (0, react_1.useEffect)(() => {
50
+ if (!enabled)
51
+ return;
52
+ const quickFilter = findQuickFilter(filter);
53
+ // The commit that starts the restore still sees the empty initial filter.
54
+ if (phaseRef.current === 'restoring') {
55
+ if (!quickFilter)
56
+ return;
57
+ phaseRef.current = 'validating';
58
+ }
59
+ if (phaseRef.current === 'validating' && initial) {
60
+ if (isSearchLoading)
61
+ searchedRef.current = true;
62
+ const settled = searchedRef.current && !isSearchLoading && !searchError && !query.trim();
63
+ const { present, values: known, counted } = groupFacetValues(facets, groupField);
64
+ if ((known.length && !counted) || (settled && present)) {
65
+ phaseRef.current = 'done';
66
+ const values = quickFilter === null || quickFilter === void 0 ? void 0 : quickFilter.values.filter((value) => known.includes(value) || !initial.includes(value));
67
+ if (quickFilter && values && values.length !== quickFilter.values.length) {
68
+ // oxlint-disable-next-line react-you-might-not-need-an-effect/no-pass-data-to-parent -- the facets arrive after the restore
69
+ setFilter((current) => withQuickFilter(current, { field: groupField, values }));
70
+ return;
71
+ }
72
+ }
73
+ }
74
+ const next = quickFilter ? { field: quickFilter.field, values: quickFilter.values } : null;
75
+ if (JSON.stringify(next) !== JSON.stringify(stored)) {
76
+ setStored(next);
77
+ }
78
+ }, [
79
+ enabled,
80
+ filter,
81
+ facets,
82
+ groupField,
83
+ query,
84
+ isSearchLoading,
85
+ searchError,
86
+ initial,
87
+ stored,
88
+ setFilter,
89
+ setStored,
90
+ ]);
91
+ }
92
+ //# sourceMappingURL=use-persisted-quick-filter.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.create.app.dialog.callbackUrls' | 'dev.create.app.dialog.callbackUrls.placeholder' | 'dev.create.app.dialog.callbackUrls.hint' | 'dev.app.callbackUrls.title' | 'dev.edit.callbackUrls.dialog.title' | 'dev.edit.callbackUrls.dialog.placeholder' | 'dev.edit.callbackUrls.dialog.hint' | 'dev.edit.callbackUrls.dialog.save' | 'dev.edit.callbackUrls.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.error.title' | 'search.error.button' | 'search.filter.title' | 'search.filter.reset' | 'search.filter.field.reset' | 'search.ai.welcomeText' | 'search.ai.newConversation' | 'search.ai.backToSearch' | 'search.ai.back' | 'search.ai.assistant' | 'search.ai.placeholder' | 'search.ai.generatingResponse' | 'search.ai.followUpQuestion' | 'search.ai.send' | 'search.ai.suggestionsTitle' | 'search.ai.thinkingText' | 'search.ai.resourcesFound' | 'search.ai.resourcesFound.basedOn' | 'search.ai.resourcesFound.resources' | 'search.ai.feedback.title' | 'search.ai.feedback.detailsPlaceholder' | 'search.ai.feedback.thanks' | 'search.ai.toolCall.executed' | 'search.ai.toolCall.executing' | 'search.ai.toolCall.usedTools' | 'search.ai.button' | 'search.ai.label' | 'search.ai.disclaimer' | 'search.ai.error.description' | 'search.ai.error.description.forbidden' | 'search.ai.error.description.unauthorized' | 'search.ai.error.header' | 'search.ai.error.header.forbidden' | 'search.ai.error.header.unauthorized' | 'search.ai.feedback.more' | 'search.searchItem.deprecated' | 'search.searchItem.enum' | 'search.searchItem.example' | 'search.groups.all' | 'search.filter.field.footer' | 'aiAssistant.trigger' | 'toc.header' | 'footer.copyrightText' | 'page.homeButton' | 'page.forbidden.title' | 'page.forbidden.description' | 'page.notFound.title' | 'page.notFound.description' | 'page.apiNotFound.title' | 'page.apiNotFound.description' | 'page.apiNotFound.hint' | 'page.apiNotFound.backButton' | 'page.mcpNotAllowed.title' | 'page.mcpNotAllowed.description' | 'page.mcpNotAllowed.publicNote' | 'page.mcpNotAllowed.publicEndpointHint' | 'page.mcpNotAllowed.mainEndpointHint' | 'page.mcpNotAllowed.snippets.terminal.hint' | 'page.mcpNotAllowed.snippets.claudeCode.headline' | 'page.mcpNotAllowed.snippets.claudeDesktop.headline' | 'page.mcpNotAllowed.snippets.claudeDesktop.instruction' | 'page.mcpNotAllowed.snippets.chatgptApp.headline' | 'page.mcpNotAllowed.snippets.chatgptApp.instruction' | 'page.mcpNotAllowed.snippets.codexCli.headline' | 'page.mcpNotAllowed.snippets.prompt.headline' | 'page.mcpNotAllowed.snippets.prompt.hint' | 'page.mcpNotAllowed.snippets.json.hint' | 'page.mcpNotAllowed.tabs.claude' | 'page.mcpNotAllowed.tabs.chatgpt' | 'page.mcpNotAllowed.tabs.json' | 'page.lastUpdated.timeago' | 'page.lastUpdated.on' | 'catalog.filters.placeholder' | 'catalog.filters.title' | 'catalog.filters.add' | 'catalog.filters.clearAll' | 'catalog.filters.select.addFilter' | 'catalog.filters.select.all' | 'catalog.filters.done' | 'catalog.catalogs.all.title' | 'catalog.catalogs.all.description' | 'catalog.catalogs.all.switcherLabel' | 'catalog.catalogs.service.title' | 'catalog.catalogs.service.description' | 'catalog.catalogs.service.switcherLabel' | 'catalog.catalogs.user.title' | 'catalog.catalogs.user.description' | 'catalog.catalogs.user.switcherLabel' | 'catalog.catalogs.team.title' | 'catalog.catalogs.team.description' | 'catalog.catalogs.team.switcherLabel' | 'catalog.catalogs.domain.title' | 'catalog.catalogs.domain.description' | 'catalog.catalogs.domain.switcherLabel' | 'catalog.catalogs.apiDescription.title' | 'catalog.catalogs.apiDescription.description' | 'catalog.catalogs.apiDescription.switcherLabel' | 'catalog.catalogs.dataSchema.title' | 'catalog.catalogs.dataSchema.description' | 'catalog.catalogs.dataSchema.switcherLabel' | 'catalog.catalogs.apiOperation.title' | 'catalog.catalogs.apiOperation.description' | 'catalog.catalogs.apiOperation.switcherLabel' | 'catalog.entity.metadata.title' | 'catalog.entity.schema.title' | 'catalog.entity.properties.apiDescription.title' | 'catalog.backToAllLabel' | 'catalog.notConnected' | 'catalog.tags.label' | 'catalog.sort' | 'catalog.catalogs.label' | 'catalog.owners.label' | 'catalog.repositories.label' | 'catalog.email.label' | 'catalog.format.label' | 'catalog.entityType.label' | 'catalog.domains.label' | 'catalog.contact.label' | 'catalog.methodAndPath.label' | 'catalog.links.label' | 'catalog.metadata.domains' | 'catalog.metadata.owners' | 'catalog.history.button.label' | 'catalog.history.sidebar.title' | 'catalog.history.sidebar.close' | 'catalog.history.version.label' | 'catalog.filters.close' | 'catalog.history.version.notSpecified' | 'catalog.history.version.default' | 'catalog.history.revisions.limitMessage' | 'catalog.history.revision.current' | 'catalog.history.revisions.showLess' | 'catalog.history.revisions.showMore' | 'navigationTabs.label' | 'sidebar.menu.backLabel' | 'sidebar.menu.backToLabel' | 'sidebar.actions.show' | 'sidebar.actions.hide' | 'sidebar.actions.changeToSingleColumn' | 'sidebar.actions.changeToTwoColumns' | 'sidebar.actions.singleColumn' | 'sidebar.actions.twoColumns' | '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.maxLength' | 'feedback.settings.comment.satisfiedLabel' | 'feedback.settings.comment.neutralLabel' | 'feedback.settings.comment.dissatisfiedLabel' | 'feedback.settings.submitText' | 'feedback.settings.label' | 'feedback.settings.reasons.label' | 'feedback.submit' | 'feedback.cancel' | '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' | 'codeSnippet.expand.tooltipText' | 'codeSnippet.collapse.tooltipText' | 'jsonViewer.showMore' | 'jsonViewer.showLess' | 'userMenu.login' | 'userMenu.logout' | 'userMenu.devOnboardingLabel' | 'mobileMenu.mainMenu' | 'mobileMenu.previous' | 'mobileMenu.products' | 'mobileMenu.version' | 'navbar.label' | 'navbar.products' | 'page.nextButton' | 'page.previousButton' | 'page.actions.copyButtonText' | 'page.actions.copyTitle' | 'page.actions.copyDescription' | 'page.actions.viewAsMdTitle' | 'page.actions.viewAsMdButtonText' | 'page.actions.viewAsMdDescription' | 'page.actions.chatGptTitle' | 'page.actions.chatGptButtonText' | 'page.actions.chatGptDescription' | 'page.actions.claudeTitle' | 'page.actions.claudeButtonText' | 'page.actions.claudeDescription' | 'page.actions.cursorMcpButtonText' | 'page.actions.cursorMcpTitle' | 'page.actions.cursorMcpDescription' | 'page.actions.connectMcp' | 'page.actions.connectMcp.cursor' | 'page.actions.connectMcp.cursorDescription' | 'page.actions.connectMcp.vscode' | 'page.actions.connectMcp.vscodeDescription' | 'page.actions.connectMcp.claudeCode' | 'page.actions.connectMcp.claudeCodeDescription' | 'page.actions.connectMcp.codex' | 'page.actions.connectMcp.codexDescription' | 'page.actions.connectMcp.copyConfig' | 'page.actions.connectMcp.copyConfigDescription' | 'page.actions.connectMcp.moreOptions' | 'page.actions.moreActions' | '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' | 'graphql.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.languages.moreButton.tooltipText' | 'openapi.servers.title' | 'openapi.operations' | 'openapi.schemas' | 'openapi.webhooks' | 'openapi.description' | 'openapi.badges.deprecated' | 'openapi.badges.required' | 'openapi.badges.webhook' | 'openapi.request' | 'openapi.path' | 'openapi.query' | 'openapi.querystring' | '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.property' | 'openapi.properties' | 'openapi.details' | 'openapi.hideExample' | 'openapi.showExample' | 'openapi.expandAll' | 'openapi.deepLink.tooltip' | 'openapi.collapseAll' | 'openapi.viewSecurityDetails' | 'openapi.noResponseExample' | 'openapi.discriminator.searchPlaceholder' | 'openapi.discriminator.searchNoResults' | 'openapi.discriminator.defaultMapping' | 'openapi.discriminator.defaultMappingTooltip' | 'openapi.noResponseContent' | 'openapi.noRequestPayload' | 'openapi.noRequestData' | 'openapi.hidePattern' | 'openapi.showPattern' | 'openapi.authorizationUrl' | 'openapi.tokenUrl' | 'openapi.refreshUrl' | 'openapi.showOptionalScopes' | 'openapi.hideOptionalScopes' | 'openapi.security' | 'openapi.httpAuthorizationScheme' | 'openapi.bearerFormat' | 'openapi.parameterName' | 'openapi.flowType' | 'openapi.connectUrl' | 'openapi.requiredScopes' | 'openapi.unsupportedLanguage' | 'openapi.failedToGenerateCodeSample' | 'openapi.schemaCatalogLink.title' | 'openapi.schemaCatalogLink.copyButtonTooltip' | 'openapi.schemaCatalogLink.copiedTooltip' | 'openapi.mcp.title' | 'openapi.mcp.endpoint' | 'openapi.mcp.tools' | 'openapi.mcp.protocolVersion' | 'openapi.mcp.capabilities' | 'openapi.mcp.experimentalCapabilities' | 'openapi.mcp.inputSchema' | 'openapi.mcp.inputExample' | 'openapi.mcp.outputSchema' | 'openapi.mcp.outputExample' | 'openapi.mcp.exampleTitle' | 'openapi.mcp.argumentsExample' | 'asyncapi.download.description.title' | 'asyncapi.info.title' | 'asyncapi.info.contact.url' | 'asyncapi.info.contact.name' | 'asyncapi.info.license' | 'asyncapi.info.termsOfService' | 'asyncapi.key' | 'asyncapi.value' | 'asyncapi.variables' | 'asyncapi.required' | 'asyncapi.requiredScopes' | 'asyncapi.viewSecurityDetails' | 'graphql.download.description.title' | 'graphql.info.title' | 'graphql.info.contact.url' | 'graphql.info.contact.name' | 'graphql.info.license' | 'graphql.info.termsOfService' | 'graphql.overview' | 'graphql.metadata' | 'graphql.key' | 'graphql.value' | 'graphql.queries' | 'graphql.mutations' | 'graphql.subscriptions' | 'graphql.directives' | 'graphql.objects' | 'graphql.interfaces' | 'graphql.unions' | 'graphql.enums' | 'graphql.inputs' | 'graphql.scalars' | 'graphql.actions.show' | 'graphql.actions.hide' | 'graphql.actions.more' | 'graphql.fields' | 'graphql.inputFields' | 'graphql.values' | '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.requiredScopes' | 'graphql.viewSecurityDetails' | 'graphql.objectScopes' | 'graphql.fieldScopes' | 'graphql.implementedInterfaces' | 'graphql.implementedBy' | 'graphql.nonNull' | 'graphql.required' | 'asyncapi.badges.deprecated' | 'graphql.badges.deprecated' | 'asyncapi.actions.show' | 'asyncapi.actions.hide' | 'asyncapi.actions.more' | 'asyncapi.payload' | 'asyncapi.parameters' | 'asyncapi.messages' | 'asyncapi.contentType' | 'asyncapi.moreDetails' | 'asyncapi.property' | 'asyncapi.properties' | 'asyncapi.details' | 'asyncapi.reply' | 'asyncapi.servers.title' | 'graphql.variables' | 'graphql.querySample' | 'graphql.mutationSample' | 'graphql.subscriptionSample' | 'graphql.responseSample' | 'graphql.locations' | 'graphql.sample' | 'graphql.referenced' | 'graphql.content.fragment' | 'button.download.tooltipText' | 'button.externalLink.tooltipText' | 'button.email.tooltipText' | 'codeWalkthrough.download' | 'codeWalkthrough.preview' | 'time.justNow' | 'time.past.second' | 'time.past.seconds' | 'time.past.minute' | 'time.past.minutes' | 'time.past.hour' | 'time.past.hours' | 'time.past.day' | 'time.past.days' | 'time.past.week' | 'time.past.weeks' | 'time.past.month' | 'time.past.months' | 'time.past.year' | 'time.past.years' | 'page.internalServerError.title' | 'page.internalServerError.description' | 'page.skipToContent.label' | 'select.noResults' | 'loaders.loading' | 'filter.dateRange.from' | 'filter.dateRange.to' | 'diagram.openFullscreen' | 'diagram.zoomIn' | 'diagram.zoomOut' | 'diagram.reset' | 'diagram.close' | 'diagram.viewer' | 'consent.title' | 'consent.message' | 'consent.privacyPolicy' | 'consent.customize' | 'consent.rejectAll' | 'consent.acceptAll' | 'consent.acceptAllCookies' | 'consent.savePreferences' | 'consent.dialog.title' | 'consent.dialog.description' | 'consent.dialog.close' | 'consent.alwaysOn' | 'consent.alwaysOff' | 'consent.gpcNotice' | 'consent.category.necessary.title' | 'consent.category.necessary.description' | 'consent.category.analytics.title' | 'consent.category.analytics.description' | 'consent.category.marketing.title' | 'consent.category.marketing.description' | 'consent.settingsLink';
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.create.app.dialog.callbackUrls' | 'dev.create.app.dialog.callbackUrls.placeholder' | 'dev.create.app.dialog.callbackUrls.hint' | 'dev.app.callbackUrls.title' | 'dev.edit.callbackUrls.dialog.title' | 'dev.edit.callbackUrls.dialog.placeholder' | 'dev.edit.callbackUrls.dialog.hint' | 'dev.edit.callbackUrls.dialog.save' | 'dev.edit.callbackUrls.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.error.title' | 'search.error.button' | 'search.filter.title' | 'search.filter.reset' | 'search.filter.field.reset' | 'search.ai.welcomeText' | 'search.ai.newConversation' | 'search.ai.backToSearch' | 'search.ai.back' | 'search.ai.assistant' | 'search.ai.placeholder' | 'search.ai.generatingResponse' | 'search.ai.followUpQuestion' | 'search.ai.send' | 'search.ai.suggestionsTitle' | 'search.ai.thinkingText' | 'search.ai.resourcesFound' | 'search.ai.resourcesFound.basedOn' | 'search.ai.resourcesFound.resources' | 'search.ai.feedback.title' | 'search.ai.feedback.detailsPlaceholder' | 'search.ai.feedback.thanks' | 'search.ai.toolCall.executed' | 'search.ai.toolCall.executing' | 'search.ai.toolCall.usedTools' | 'search.ai.button' | 'search.ai.label' | 'search.ai.disclaimer' | 'search.ai.error.description' | 'search.ai.error.description.forbidden' | 'search.ai.error.description.unauthorized' | 'search.ai.error.header' | 'search.ai.error.header.forbidden' | 'search.ai.error.header.unauthorized' | 'search.ai.feedback.more' | 'search.ai.actions.copy' | 'search.ai.actions.like' | 'search.ai.actions.dislike' | 'search.searchItem.deprecated' | 'search.searchItem.enum' | 'search.searchItem.example' | 'search.groups.all' | 'search.filter.field.footer' | 'aiAssistant.trigger' | 'toc.header' | 'footer.copyrightText' | 'page.homeButton' | 'page.forbidden.title' | 'page.forbidden.description' | 'page.notFound.title' | 'page.notFound.description' | 'page.apiNotFound.title' | 'page.apiNotFound.description' | 'page.apiNotFound.hint' | 'page.apiNotFound.backButton' | 'page.mcpNotAllowed.title' | 'page.mcpNotAllowed.description' | 'page.mcpNotAllowed.publicNote' | 'page.mcpNotAllowed.publicEndpointHint' | 'page.mcpNotAllowed.mainEndpointHint' | 'page.mcpNotAllowed.snippets.terminal.hint' | 'page.mcpNotAllowed.snippets.claudeCode.headline' | 'page.mcpNotAllowed.snippets.claudeDesktop.headline' | 'page.mcpNotAllowed.snippets.claudeDesktop.instruction' | 'page.mcpNotAllowed.snippets.chatgptApp.headline' | 'page.mcpNotAllowed.snippets.chatgptApp.instruction' | 'page.mcpNotAllowed.snippets.codexCli.headline' | 'page.mcpNotAllowed.snippets.prompt.headline' | 'page.mcpNotAllowed.snippets.prompt.hint' | 'page.mcpNotAllowed.snippets.json.hint' | 'page.mcpNotAllowed.tabs.claude' | 'page.mcpNotAllowed.tabs.chatgpt' | 'page.mcpNotAllowed.tabs.json' | 'page.lastUpdated.timeago' | 'page.lastUpdated.on' | 'catalog.filters.placeholder' | 'catalog.filters.title' | 'catalog.filters.add' | 'catalog.filters.clearAll' | 'catalog.filters.select.addFilter' | 'catalog.filters.select.all' | 'catalog.filters.done' | 'catalog.catalogs.all.title' | 'catalog.catalogs.all.description' | 'catalog.catalogs.all.switcherLabel' | 'catalog.catalogs.service.title' | 'catalog.catalogs.service.description' | 'catalog.catalogs.service.switcherLabel' | 'catalog.catalogs.user.title' | 'catalog.catalogs.user.description' | 'catalog.catalogs.user.switcherLabel' | 'catalog.catalogs.team.title' | 'catalog.catalogs.team.description' | 'catalog.catalogs.team.switcherLabel' | 'catalog.catalogs.domain.title' | 'catalog.catalogs.domain.description' | 'catalog.catalogs.domain.switcherLabel' | 'catalog.catalogs.apiDescription.title' | 'catalog.catalogs.apiDescription.description' | 'catalog.catalogs.apiDescription.switcherLabel' | 'catalog.catalogs.dataSchema.title' | 'catalog.catalogs.dataSchema.description' | 'catalog.catalogs.dataSchema.switcherLabel' | 'catalog.catalogs.apiOperation.title' | 'catalog.catalogs.apiOperation.description' | 'catalog.catalogs.apiOperation.switcherLabel' | 'catalog.entity.metadata.title' | 'catalog.entity.schema.title' | 'catalog.entity.properties.apiDescription.title' | 'catalog.backToAllLabel' | 'catalog.notConnected' | 'catalog.tags.label' | 'catalog.sort' | 'catalog.catalogs.label' | 'catalog.owners.label' | 'catalog.repositories.label' | 'catalog.email.label' | 'catalog.format.label' | 'catalog.entityType.label' | 'catalog.domains.label' | 'catalog.contact.label' | 'catalog.methodAndPath.label' | 'catalog.links.label' | 'catalog.metadata.domains' | 'catalog.metadata.owners' | 'catalog.history.button.label' | 'catalog.history.sidebar.title' | 'catalog.history.sidebar.close' | 'catalog.history.version.label' | 'catalog.filters.close' | 'catalog.history.version.notSpecified' | 'catalog.history.version.default' | 'catalog.history.revisions.limitMessage' | 'catalog.history.revision.current' | 'catalog.history.revisions.showLess' | 'catalog.history.revisions.showMore' | 'navigationTabs.label' | 'sidebar.menu.backLabel' | 'sidebar.menu.backToLabel' | 'sidebar.actions.show' | 'sidebar.actions.hide' | 'sidebar.actions.changeToSingleColumn' | 'sidebar.actions.changeToTwoColumns' | 'sidebar.actions.singleColumn' | 'sidebar.actions.twoColumns' | '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.maxLength' | 'feedback.settings.comment.satisfiedLabel' | 'feedback.settings.comment.neutralLabel' | 'feedback.settings.comment.dissatisfiedLabel' | 'feedback.settings.submitText' | 'feedback.settings.label' | 'feedback.settings.reasons.label' | 'feedback.submit' | 'feedback.cancel' | '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' | 'codeSnippet.expand.tooltipText' | 'codeSnippet.collapse.tooltipText' | 'jsonViewer.showMore' | 'jsonViewer.showLess' | 'userMenu.login' | 'userMenu.logout' | 'userMenu.devOnboardingLabel' | 'mobileMenu.mainMenu' | 'mobileMenu.previous' | 'mobileMenu.products' | 'mobileMenu.version' | 'navbar.label' | 'navbar.products' | 'page.nextButton' | 'page.previousButton' | 'page.actions.copyButtonText' | 'page.actions.copyTitle' | 'page.actions.copyDescription' | 'page.actions.viewAsMdTitle' | 'page.actions.viewAsMdButtonText' | 'page.actions.viewAsMdDescription' | 'page.actions.chatGptTitle' | 'page.actions.chatGptButtonText' | 'page.actions.chatGptDescription' | 'page.actions.claudeTitle' | 'page.actions.claudeButtonText' | 'page.actions.claudeDescription' | 'page.actions.cursorMcpButtonText' | 'page.actions.cursorMcpTitle' | 'page.actions.cursorMcpDescription' | 'page.actions.connectMcp' | 'page.actions.connectMcp.cursor' | 'page.actions.connectMcp.cursorDescription' | 'page.actions.connectMcp.vscode' | 'page.actions.connectMcp.vscodeDescription' | 'page.actions.connectMcp.claudeCode' | 'page.actions.connectMcp.claudeCodeDescription' | 'page.actions.connectMcp.codex' | 'page.actions.connectMcp.codexDescription' | 'page.actions.connectMcp.copyConfig' | 'page.actions.connectMcp.copyConfigDescription' | 'page.actions.connectMcp.moreOptions' | 'page.actions.moreActions' | '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' | 'graphql.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.languages.moreButton.tooltipText' | 'openapi.servers.title' | 'openapi.operations' | 'openapi.schemas' | 'openapi.webhooks' | 'openapi.description' | 'openapi.badges.deprecated' | 'openapi.badges.required' | 'openapi.badges.webhook' | 'openapi.request' | 'openapi.path' | 'openapi.query' | 'openapi.querystring' | '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.property' | 'openapi.properties' | 'openapi.details' | 'openapi.hideExample' | 'openapi.showExample' | 'openapi.expandAll' | 'openapi.deepLink.tooltip' | 'openapi.collapseAll' | 'openapi.viewSecurityDetails' | 'openapi.noResponseExample' | 'openapi.discriminator.searchPlaceholder' | 'openapi.discriminator.searchNoResults' | 'openapi.discriminator.defaultMapping' | 'openapi.discriminator.defaultMappingTooltip' | 'openapi.noResponseContent' | 'openapi.noRequestPayload' | 'openapi.noRequestData' | 'openapi.hidePattern' | 'openapi.showPattern' | 'openapi.authorizationUrl' | 'openapi.tokenUrl' | 'openapi.refreshUrl' | 'openapi.showOptionalScopes' | 'openapi.hideOptionalScopes' | 'openapi.security' | 'openapi.httpAuthorizationScheme' | 'openapi.bearerFormat' | 'openapi.parameterName' | 'openapi.flowType' | 'openapi.connectUrl' | 'openapi.requiredScopes' | 'openapi.unsupportedLanguage' | 'openapi.failedToGenerateCodeSample' | 'openapi.schemaCatalogLink.title' | 'openapi.schemaCatalogLink.copyButtonTooltip' | 'openapi.schemaCatalogLink.copiedTooltip' | 'openapi.mcp.title' | 'openapi.mcp.endpoint' | 'openapi.mcp.tools' | 'openapi.mcp.protocolVersion' | 'openapi.mcp.capabilities' | 'openapi.mcp.experimentalCapabilities' | 'openapi.mcp.inputSchema' | 'openapi.mcp.inputExample' | 'openapi.mcp.outputSchema' | 'openapi.mcp.outputExample' | 'openapi.mcp.exampleTitle' | 'openapi.mcp.argumentsExample' | 'asyncapi.download.description.title' | 'asyncapi.info.title' | 'asyncapi.info.contact.url' | 'asyncapi.info.contact.name' | 'asyncapi.info.license' | 'asyncapi.info.termsOfService' | 'asyncapi.key' | 'asyncapi.value' | 'asyncapi.variables' | 'asyncapi.required' | 'asyncapi.requiredScopes' | 'asyncapi.viewSecurityDetails' | 'graphql.download.description.title' | 'graphql.info.title' | 'graphql.info.contact.url' | 'graphql.info.contact.name' | 'graphql.info.license' | 'graphql.info.termsOfService' | 'graphql.overview' | 'graphql.metadata' | 'graphql.key' | 'graphql.value' | 'graphql.queries' | 'graphql.mutations' | 'graphql.subscriptions' | 'graphql.directives' | 'graphql.objects' | 'graphql.interfaces' | 'graphql.unions' | 'graphql.enums' | 'graphql.inputs' | 'graphql.scalars' | 'graphql.actions.show' | 'graphql.actions.hide' | 'graphql.actions.more' | 'graphql.fields' | 'graphql.inputFields' | 'graphql.values' | '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.requiredScopes' | 'graphql.viewSecurityDetails' | 'graphql.objectScopes' | 'graphql.fieldScopes' | 'graphql.implementedInterfaces' | 'graphql.implementedBy' | 'graphql.nonNull' | 'graphql.required' | 'asyncapi.badges.deprecated' | 'graphql.badges.deprecated' | 'asyncapi.actions.show' | 'asyncapi.actions.hide' | 'asyncapi.actions.more' | 'asyncapi.payload' | 'asyncapi.parameters' | 'asyncapi.messages' | 'asyncapi.contentType' | 'asyncapi.moreDetails' | 'asyncapi.property' | 'asyncapi.properties' | 'asyncapi.details' | 'asyncapi.reply' | 'asyncapi.servers.title' | 'graphql.variables' | 'graphql.querySample' | 'graphql.mutationSample' | 'graphql.subscriptionSample' | 'graphql.responseSample' | 'graphql.locations' | 'graphql.sample' | 'graphql.referenced' | 'graphql.content.fragment' | 'button.download.tooltipText' | 'button.externalLink.tooltipText' | 'button.email.tooltipText' | 'codeWalkthrough.download' | 'codeWalkthrough.preview' | 'time.justNow' | 'time.past.second' | 'time.past.seconds' | 'time.past.minute' | 'time.past.minutes' | 'time.past.hour' | 'time.past.hours' | 'time.past.day' | 'time.past.days' | 'time.past.week' | 'time.past.weeks' | 'time.past.month' | 'time.past.months' | 'time.past.year' | 'time.past.years' | 'page.internalServerError.title' | 'page.internalServerError.description' | 'page.skipToContent.label' | 'select.noResults' | 'loaders.loading' | 'filter.dateRange.from' | 'filter.dateRange.to' | 'diagram.openFullscreen' | 'diagram.zoomIn' | 'diagram.zoomOut' | 'diagram.reset' | 'diagram.close' | 'diagram.viewer' | 'consent.title' | 'consent.message' | 'consent.privacyPolicy' | 'consent.customize' | 'consent.rejectAll' | 'consent.acceptAll' | 'consent.acceptAllCookies' | 'consent.savePreferences' | 'consent.dialog.title' | 'consent.dialog.description' | 'consent.dialog.close' | 'consent.alwaysOn' | 'consent.alwaysOff' | 'consent.gpcNotice' | 'consent.category.necessary.title' | 'consent.category.necessary.description' | 'consent.category.analytics.title' | 'consent.category.analytics.description' | 'consent.category.marketing.title' | 'consent.category.marketing.description' | 'consent.settingsLink';
3
3
  export type Locale = {
4
4
  code: string;
5
5
  name: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@redocly/theme",
3
- "version": "0.70.0-next.6",
3
+ "version": "0.70.0-next.7",
4
4
  "description": "Shared UI components lib",
5
5
  "keywords": [
6
6
  "theme",
@@ -43,6 +43,7 @@
43
43
  "@babel/core": "7.29.7",
44
44
  "@markdoc/markdoc": "0.5.2",
45
45
  "@redocly/realm-asyncapi-sdk": "0.16.0-next.2",
46
+ "@redocly/tooling-test-utils": "0.0.0",
46
47
  "@shikijs/types": "3.21.0",
47
48
  "@testing-library/react": "16.3.0",
48
49
  "@testing-library/user-event": "14.6.1",
@@ -1,9 +1,10 @@
1
- import React from 'react';
1
+ import React, { useContext } from 'react';
2
2
  import { createPortal } from 'react-dom';
3
3
 
4
4
  import type { PropsWithChildren, JSX } from 'react';
5
5
 
6
6
  import { IS_BROWSER } from '@redocly/theme/core/utils';
7
+ import { PortalContainerContext } from '@redocly/theme/core/contexts/PortalContainerContext';
7
8
 
8
9
  export type PortalProps = {
9
10
  mountId?: string;
@@ -13,7 +14,8 @@ export function Portal({
13
14
  children,
14
15
  mountId = 'root',
15
16
  }: PropsWithChildren<PortalProps>): React.ReactPortal | JSX.Element {
16
- const mount = IS_BROWSER ? document.getElementById(mountId) || document.body : null;
17
+ const container = useContext(PortalContainerContext);
18
+ const mount = IS_BROWSER ? container || document.getElementById(mountId) || document.body : null;
17
19
 
18
20
  if (!mount) {
19
21
  return <>{children}</>;
@@ -10,6 +10,8 @@ import { ThumbUpFilledIcon } from '@redocly/theme/icons/ThumbUpFilledIcon/ThumbU
10
10
  import { ThumbDownIcon } from '@redocly/theme/icons/ThumbDownIcon/ThumbDownIcon';
11
11
  import { ThumbDownFilledIcon } from '@redocly/theme/icons/ThumbDownFilledIcon/ThumbDownFilledIcon';
12
12
  import { CopyButton } from '@redocly/theme/components/Buttons/CopyButton';
13
+ import { Tooltip } from '@redocly/theme/components/Tooltip/Tooltip';
14
+ import { useThemeHooks } from '@redocly/theme/core/hooks';
13
15
 
14
16
  export type SearchAiActionButtonsProps = {
15
17
  content: string;
@@ -26,28 +28,38 @@ export function SearchAiActionButtons({
26
28
  onFeedback,
27
29
  disabled,
28
30
  }: SearchAiActionButtonsProps): JSX.Element {
31
+ const { useTranslate } = useThemeHooks();
32
+ const { translate } = useTranslate();
33
+
29
34
  return (
30
35
  <ActionButtonsWrapper className={className} data-component-name="Search/SearchAiActionButtons">
31
- <CopyButton data={content} />
32
- <FeedbackButton
33
- variant="text"
34
- size="small"
35
- icon={feedback === FeedbackType.Like ? <ThumbUpFilledIcon /> : <ThumbUpIcon />}
36
- onClick={() => !disabled && onFeedback(FeedbackType.Like)}
37
- extraClass={feedback === FeedbackType.Like ? 'active' : ''}
38
- aria-label="Like this response"
39
- disabled={disabled}
40
- />
36
+ <Tooltip tip={translate('search.ai.actions.copy', 'Copy the response')} placement="top">
37
+ <CopyButton data={content} />
38
+ </Tooltip>
39
+
40
+ <Tooltip tip={translate('search.ai.actions.like', 'Like the response')} placement="top">
41
+ <FeedbackButton
42
+ variant="text"
43
+ size="small"
44
+ icon={feedback === FeedbackType.Like ? <ThumbUpFilledIcon /> : <ThumbUpIcon />}
45
+ onClick={() => !disabled && onFeedback(FeedbackType.Like)}
46
+ extraClass={feedback === FeedbackType.Like ? 'active' : ''}
47
+ aria-label="Like this response"
48
+ disabled={disabled}
49
+ />
50
+ </Tooltip>
41
51
 
42
- <FeedbackButton
43
- variant="text"
44
- size="small"
45
- icon={feedback === FeedbackType.Dislike ? <ThumbDownFilledIcon /> : <ThumbDownIcon />}
46
- onClick={() => !disabled && onFeedback(FeedbackType.Dislike)}
47
- extraClass={feedback === FeedbackType.Dislike ? 'active' : ''}
48
- aria-label="Dislike this response"
49
- disabled={disabled}
50
- />
52
+ <Tooltip tip={translate('search.ai.actions.dislike', 'Dislike the response')} placement="top">
53
+ <FeedbackButton
54
+ variant="text"
55
+ size="small"
56
+ icon={feedback === FeedbackType.Dislike ? <ThumbDownFilledIcon /> : <ThumbDownIcon />}
57
+ onClick={() => !disabled && onFeedback(FeedbackType.Dislike)}
58
+ extraClass={feedback === FeedbackType.Dislike ? 'active' : ''}
59
+ aria-label="Dislike this response"
60
+ disabled={disabled}
61
+ />
62
+ </Tooltip>
51
63
  </ActionButtonsWrapper>
52
64
  );
53
65
  }
@@ -19,6 +19,7 @@ import {
19
19
  useSearchFilter,
20
20
  useRecentSearches,
21
21
  useModalScrollLock,
22
+ usePersistedQuickFilter,
22
23
  } from '@redocly/theme/core/hooks';
23
24
  import { Tag } from '@redocly/theme/components/Tag/Tag';
24
25
  import { CloseIcon } from '@redocly/theme/icons/CloseIcon/CloseIcon';
@@ -81,6 +82,16 @@ export function SearchDialog({
81
82
  onFacetReset,
82
83
  onQuickFilterReset,
83
84
  } = useSearchFilter(filter, setFilter);
85
+ usePersistedQuickFilter({
86
+ filter,
87
+ setFilter,
88
+ facets,
89
+ groupField,
90
+ query,
91
+ isSearchLoading,
92
+ searchError,
93
+ enabled: initialMode === 'search',
94
+ });
84
95
  const { addSearchHistoryItem } = useRecentSearches();
85
96
  const aiSearch = useAiSearch({ filter: filter, product: product?.name });
86
97
  useModalScrollLock(true);
@@ -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);
@@ -24,6 +24,7 @@ export * from '../feedback/use-report-dialog';
24
24
  export * from '../use-product-picker';
25
25
  export * from '../search/use-search-dialog';
26
26
  export * from '../search/use-search-ai-activity';
27
+ export * from '../search/use-persisted-quick-filter';
27
28
  export * from '../use-language-picker';
28
29
  export * from './use-element-size';
29
30
  export * from './use-time-ago';
@@ -31,6 +31,7 @@ export * from './search/use-search-dialog';
31
31
  export * from './search/use-search-filter';
32
32
  export * from './search/use-feedback-tooltip';
33
33
  export * from './search/use-search-ai-activity';
34
+ export * from './search/use-persisted-quick-filter';
34
35
  export * from './use-controlled-state';
35
36
  export * from './use-codeblock-tabs-controls';
36
37
  export * from './code-walkthrough/use-code-walkthrough';
@@ -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
+ }
@@ -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'