@redocly/theme 0.64.0 → 0.65.0-next.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/components/Tooltip/AnchorTooltip.js +11 -2
- package/lib/components/Tooltip/JsTooltip.js +28 -47
- package/lib/components/Tooltip/Tooltip.d.ts +1 -1
- package/lib/components/Tooltip/Tooltip.js +5 -2
- package/lib/core/hooks/index.d.ts +1 -0
- package/lib/core/hooks/index.js +1 -0
- package/lib/core/hooks/use-tooltip-fallback-placement.d.ts +15 -0
- package/lib/core/hooks/use-tooltip-fallback-placement.js +46 -0
- package/lib/core/styles/global.js +1 -0
- package/lib/core/types/l10n.d.ts +1 -1
- package/lib/core/types/tooltip.d.ts +3 -1
- package/lib/core/utils/index.d.ts +1 -0
- package/lib/core/utils/index.js +1 -0
- package/lib/core/utils/tooltip-placement.d.ts +32 -0
- package/lib/core/utils/tooltip-placement.js +92 -0
- package/package.json +3 -3
- package/src/components/Tooltip/AnchorTooltip.tsx +20 -3
- package/src/components/Tooltip/JsTooltip.tsx +36 -48
- package/src/components/Tooltip/Tooltip.tsx +14 -3
- package/src/core/hooks/__mocks__/index.ts +1 -0
- package/src/core/hooks/index.ts +1 -0
- package/src/core/hooks/use-tooltip-fallback-placement.ts +73 -0
- package/src/core/styles/global.ts +1 -0
- package/src/core/types/l10n.ts +9 -0
- package/src/core/types/tooltip.ts +4 -1
- package/src/core/utils/index.ts +1 -0
- package/src/core/utils/tooltip-placement.ts +141 -0
|
@@ -38,11 +38,18 @@ const react_1 = __importStar(require("react"));
|
|
|
38
38
|
const styled_components_1 = __importStar(require("styled-components"));
|
|
39
39
|
const hooks_1 = require("../../core/hooks");
|
|
40
40
|
const Portal_1 = require("../../components/Portal/Portal");
|
|
41
|
-
function TooltipComponent({ children, isOpen, tip, withArrow = true, placement = 'top', className = 'default', width, dataTestId, disabled = false, arrowPosition = 'center', onClick, }) {
|
|
41
|
+
function TooltipComponent({ children, isOpen, tip, withArrow = true, placement = 'top', fallbackPlacements, className = 'default', width, dataTestId, disabled = false, arrowPosition = 'center', onClick, }) {
|
|
42
42
|
const tooltipWrapperRef = (0, react_1.useRef)(null);
|
|
43
43
|
const tooltipBodyRef = (0, react_1.useRef)(null);
|
|
44
44
|
const { isOpened, handleOpen, handleClose } = (0, hooks_1.useControl)(isOpen);
|
|
45
45
|
const anchorName = `--tooltip${(0, react_1.useId)().replace(/:/g, '')}`;
|
|
46
|
+
const { activePlacement, activeArrowPosition } = (0, hooks_1.useTooltipFallbackPlacement)({
|
|
47
|
+
isOpened,
|
|
48
|
+
placement,
|
|
49
|
+
arrowPosition,
|
|
50
|
+
fallbackPlacements,
|
|
51
|
+
tooltipBodyRef,
|
|
52
|
+
});
|
|
46
53
|
(0, hooks_1.useOutsideClick)(isOpened ? [tooltipWrapperRef, tooltipBodyRef] : tooltipWrapperRef, handleClose);
|
|
47
54
|
const isControlled = isOpen !== undefined;
|
|
48
55
|
(0, react_1.useEffect)(() => {
|
|
@@ -70,7 +77,9 @@ function TooltipComponent({ children, isOpen, tip, withArrow = true, placement =
|
|
|
70
77
|
return (react_1.default.createElement(TooltipWrapper, Object.assign({ ref: tooltipWrapperRef }, controllers, { className: `tooltip-${className}`, "data-component-name": "Tooltip/Tooltip", anchorName: anchorName }),
|
|
71
78
|
children,
|
|
72
79
|
isOpened && !disabled && (react_1.default.createElement(Portal_1.Portal, null,
|
|
73
|
-
react_1.default.createElement(TooltipBody, { ref: tooltipBodyRef, "data-testid": dataTestId || (typeof tip === 'string' ? tip : ''), placement:
|
|
80
|
+
react_1.default.createElement(TooltipBody, { ref: tooltipBodyRef, "data-testid": dataTestId || (typeof tip === 'string' ? tip : ''), placement: activePlacement, width: width, withArrow: withArrow, arrowPosition: activeArrowPosition === 'left' || activeArrowPosition === 'right'
|
|
81
|
+
? activeArrowPosition
|
|
82
|
+
: 'center', anchorName: anchorName }, tip)))));
|
|
74
83
|
}
|
|
75
84
|
exports.Tooltip = (0, react_1.memo)(TooltipComponent);
|
|
76
85
|
const PLACEMENTS = {
|
|
@@ -38,56 +38,37 @@ const react_1 = __importStar(require("react"));
|
|
|
38
38
|
const styled_components_1 = __importStar(require("styled-components"));
|
|
39
39
|
const hooks_1 = require("../../core/hooks");
|
|
40
40
|
const Portal_1 = require("../../components/Portal/Portal");
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
const utils_1 = require("../../core/utils");
|
|
42
|
+
function TooltipComponent({ children, isOpen, tip, withArrow = true, placement = 'top', fallbackPlacements, className = 'default', width, dataTestId, disabled = false, arrowPosition = 'center', onClick, }) {
|
|
43
|
+
const wrapperRef = (0, react_1.useRef)(null);
|
|
44
|
+
const tooltipBodyRef = (0, react_1.useRef)(null);
|
|
43
45
|
const { isOpened, handleOpen, handleClose } = (0, hooks_1.useControl)(isOpen);
|
|
44
46
|
const [tooltipPosition, setTooltipPosition] = (0, react_1.useState)({ top: 0, left: 0 });
|
|
45
|
-
(0,
|
|
47
|
+
const [activePlacement, setActivePlacement] = (0, react_1.useState)(placement);
|
|
48
|
+
const activeArrowPosition = activePlacement === placement ? arrowPosition : 'center';
|
|
49
|
+
(0, hooks_1.useOutsideClick)(wrapperRef, handleClose);
|
|
46
50
|
const isControlled = isOpen !== undefined;
|
|
47
51
|
const updateTooltipPosition = (0, react_1.useCallback)(() => {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
top = rect.bottom;
|
|
67
|
-
if (arrowPosition === 'left') {
|
|
68
|
-
left = rect.left - 24;
|
|
69
|
-
}
|
|
70
|
-
else if (arrowPosition === 'right') {
|
|
71
|
-
left = rect.right + 24;
|
|
72
|
-
}
|
|
73
|
-
else {
|
|
74
|
-
left = rect.left + rect.width / 2;
|
|
75
|
-
}
|
|
76
|
-
break;
|
|
77
|
-
case 'left':
|
|
78
|
-
top = rect.top + rect.height / 2;
|
|
79
|
-
left = rect.left;
|
|
80
|
-
break;
|
|
81
|
-
case 'right':
|
|
82
|
-
top = rect.top + rect.height / 2;
|
|
83
|
-
left = rect.right;
|
|
84
|
-
break;
|
|
85
|
-
}
|
|
86
|
-
setTooltipPosition({ top, left });
|
|
87
|
-
}
|
|
88
|
-
}, [isOpened, placement, arrowPosition]);
|
|
52
|
+
var _a, _b, _c, _d;
|
|
53
|
+
if (!isOpened || !wrapperRef.current)
|
|
54
|
+
return;
|
|
55
|
+
const triggerRect = wrapperRef.current.getBoundingClientRect();
|
|
56
|
+
const tooltipWidth = (_b = (_a = tooltipBodyRef.current) === null || _a === void 0 ? void 0 : _a.offsetWidth) !== null && _b !== void 0 ? _b : 0;
|
|
57
|
+
const tooltipHeight = (_d = (_c = tooltipBodyRef.current) === null || _c === void 0 ? void 0 : _c.offsetHeight) !== null && _d !== void 0 ? _d : 0;
|
|
58
|
+
const resolved = (0, utils_1.resolvePlacement)({
|
|
59
|
+
triggerRect,
|
|
60
|
+
tooltipWidth,
|
|
61
|
+
tooltipHeight,
|
|
62
|
+
placement,
|
|
63
|
+
arrowPosition,
|
|
64
|
+
fallbackPlacements,
|
|
65
|
+
});
|
|
66
|
+
const resolvedArrow = resolved === placement ? arrowPosition : 'center';
|
|
67
|
+
setTooltipPosition((0, utils_1.calcAnchorPoint)(triggerRect, resolved, resolvedArrow));
|
|
68
|
+
setActivePlacement(resolved);
|
|
69
|
+
}, [isOpened, placement, arrowPosition, fallbackPlacements]);
|
|
89
70
|
(0, react_1.useLayoutEffect)(() => {
|
|
90
|
-
if (isOpened &&
|
|
71
|
+
if (isOpened && wrapperRef.current) {
|
|
91
72
|
updateTooltipPosition();
|
|
92
73
|
const handleScroll = () => updateTooltipPosition();
|
|
93
74
|
const handleResize = () => updateTooltipPosition();
|
|
@@ -118,10 +99,10 @@ function TooltipComponent({ children, isOpen, tip, withArrow = true, placement =
|
|
|
118
99
|
onFocus: handleOpen,
|
|
119
100
|
onBlur: handleClose,
|
|
120
101
|
};
|
|
121
|
-
return (react_1.default.createElement(TooltipWrapper, Object.assign({ ref:
|
|
102
|
+
return (react_1.default.createElement(TooltipWrapper, Object.assign({ ref: wrapperRef }, controllers, { className: `tooltip-${className}`, "data-component-name": "Tooltip/Tooltip" }),
|
|
122
103
|
children,
|
|
123
104
|
isOpened && !disabled && (react_1.default.createElement(Portal_1.Portal, null,
|
|
124
|
-
react_1.default.createElement(TooltipBody, { "data-testid": dataTestId || (typeof tip === 'string' ? tip : ''), placement:
|
|
105
|
+
react_1.default.createElement(TooltipBody, { ref: tooltipBodyRef, "data-testid": dataTestId || (typeof tip === 'string' ? tip : ''), placement: activePlacement, width: width, withArrow: withArrow, arrowPosition: activeArrowPosition, style: {
|
|
125
106
|
position: 'fixed',
|
|
126
107
|
top: tooltipPosition.top,
|
|
127
108
|
left: tooltipPosition.left,
|
|
@@ -38,13 +38,16 @@ const react_1 = __importStar(require("react"));
|
|
|
38
38
|
const AnchorTooltip_1 = require("../../components/Tooltip/AnchorTooltip");
|
|
39
39
|
const JsTooltip_1 = require("../../components/Tooltip/JsTooltip");
|
|
40
40
|
const hooks_1 = require("../../core/hooks");
|
|
41
|
+
const utils_1 = require("../../core/utils");
|
|
41
42
|
function TooltipComponent(props) {
|
|
43
|
+
var _a, _b;
|
|
42
44
|
const { useAnchorPositioning } = (0, hooks_1.useThemeHooks)();
|
|
43
45
|
const { isSupported } = useAnchorPositioning();
|
|
46
|
+
const fallbackPlacements = (_a = props.fallbackPlacements) !== null && _a !== void 0 ? _a : (0, utils_1.getDefaultFallbackPlacements)((_b = props.placement) !== null && _b !== void 0 ? _b : 'top');
|
|
44
47
|
if (isSupported) {
|
|
45
|
-
return react_1.default.createElement(AnchorTooltip_1.Tooltip, Object.assign({}, props, { arrowPosition: prepareArrowPosition(props.arrowPosition) }));
|
|
48
|
+
return (react_1.default.createElement(AnchorTooltip_1.Tooltip, Object.assign({}, props, { fallbackPlacements: fallbackPlacements, arrowPosition: prepareArrowPosition(props.arrowPosition) })));
|
|
46
49
|
}
|
|
47
|
-
return react_1.default.createElement(JsTooltip_1.Tooltip, Object.assign({}, props));
|
|
50
|
+
return react_1.default.createElement(JsTooltip_1.Tooltip, Object.assign({}, props, { fallbackPlacements: fallbackPlacements }));
|
|
48
51
|
}
|
|
49
52
|
exports.Tooltip = (0, react_1.memo)(TooltipComponent);
|
|
50
53
|
const prepareArrowPosition = (arrowPosition) => {
|
package/lib/core/hooks/index.js
CHANGED
|
@@ -70,4 +70,5 @@ __exportStar(require("./use-is-truncated"), exports);
|
|
|
70
70
|
__exportStar(require("./use-toast"), exports);
|
|
71
71
|
__exportStar(require("./use-toast-logic"), exports);
|
|
72
72
|
__exportStar(require("./use-banner-telemetry"), exports);
|
|
73
|
+
__exportStar(require("./use-tooltip-fallback-placement"), exports);
|
|
73
74
|
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { RefObject } from 'react';
|
|
2
|
+
import type { TooltipPlacement, TooltipProps } from '../../core/types';
|
|
3
|
+
type TooltipFallbackPlacementParams = {
|
|
4
|
+
isOpened: boolean;
|
|
5
|
+
placement: TooltipPlacement;
|
|
6
|
+
arrowPosition: TooltipProps['arrowPosition'];
|
|
7
|
+
fallbackPlacements: TooltipPlacement[] | undefined;
|
|
8
|
+
tooltipBodyRef: RefObject<HTMLElement | null>;
|
|
9
|
+
};
|
|
10
|
+
type TooltipFallbackPlacementResult = {
|
|
11
|
+
activePlacement: TooltipPlacement;
|
|
12
|
+
activeArrowPosition: TooltipProps['arrowPosition'];
|
|
13
|
+
};
|
|
14
|
+
export declare function useTooltipFallbackPlacement({ isOpened, placement, arrowPosition, fallbackPlacements, tooltipBodyRef, }: TooltipFallbackPlacementParams): TooltipFallbackPlacementResult;
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.useTooltipFallbackPlacement = useTooltipFallbackPlacement;
|
|
4
|
+
const react_1 = require("react");
|
|
5
|
+
function useTooltipFallbackPlacement({ isOpened, placement, arrowPosition, fallbackPlacements, tooltipBodyRef, }) {
|
|
6
|
+
const [activePlacement, setActivePlacement] = (0, react_1.useState)(placement);
|
|
7
|
+
const wasOpenRef = (0, react_1.useRef)(false);
|
|
8
|
+
const candidateIndexRef = (0, react_1.useRef)(0);
|
|
9
|
+
(0, react_1.useLayoutEffect)(() => {
|
|
10
|
+
if (!isOpened) {
|
|
11
|
+
wasOpenRef.current = false;
|
|
12
|
+
candidateIndexRef.current = 0;
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
if (!wasOpenRef.current) {
|
|
16
|
+
wasOpenRef.current = true;
|
|
17
|
+
candidateIndexRef.current = 0;
|
|
18
|
+
if (activePlacement !== placement) {
|
|
19
|
+
setActivePlacement(placement);
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
if (!tooltipBodyRef.current || !(fallbackPlacements === null || fallbackPlacements === void 0 ? void 0 : fallbackPlacements.length))
|
|
24
|
+
return;
|
|
25
|
+
const candidates = [placement, ...fallbackPlacements];
|
|
26
|
+
if (candidateIndexRef.current >= candidates.length)
|
|
27
|
+
return;
|
|
28
|
+
const rect = tooltipBodyRef.current.getBoundingClientRect();
|
|
29
|
+
const overflows = rect.left < 0 ||
|
|
30
|
+
rect.top < 0 ||
|
|
31
|
+
rect.right > window.innerWidth ||
|
|
32
|
+
rect.bottom > window.innerHeight;
|
|
33
|
+
if (!overflows)
|
|
34
|
+
return;
|
|
35
|
+
candidateIndexRef.current++;
|
|
36
|
+
if (candidateIndexRef.current < candidates.length) {
|
|
37
|
+
setActivePlacement(candidates[candidateIndexRef.current]);
|
|
38
|
+
}
|
|
39
|
+
else if (activePlacement !== placement) {
|
|
40
|
+
setActivePlacement(placement);
|
|
41
|
+
}
|
|
42
|
+
}, [isOpened, activePlacement, placement, fallbackPlacements, tooltipBodyRef]);
|
|
43
|
+
const activeArrowPosition = activePlacement === placement ? arrowPosition : 'center';
|
|
44
|
+
return { activePlacement, activeArrowPosition };
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=use-tooltip-fallback-placement.js.map
|
|
@@ -1232,6 +1232,7 @@ const replay = (0, styled_components_1.css) `
|
|
|
1232
1232
|
--replay-runtime-expression-color: rgba(54, 90, 249, 1); // @presenter Color
|
|
1233
1233
|
--replay-runtime-expression-bg-color: rgba(54, 90, 249, 0.08); // @presenter Color
|
|
1234
1234
|
--replay-operators-color: rgba(193, 142, 31, 1); // @presenter Color
|
|
1235
|
+
--replay-claude-icon-color: rgba(217, 119, 87, 1); // @presenter Color
|
|
1235
1236
|
|
|
1236
1237
|
--replay-ai-gradient-soft: linear-gradient(62.6deg, rgba(113, 94, 254, 0.16) 0%, rgba(255, 92, 220, 0.16) 100%);
|
|
1237
1238
|
--replay-ai-gradient-disabled: linear-gradient(62.6deg, rgba(113, 94, 254, 0.6) 0%, rgba(255, 92, 220, 0.6) 100%);
|
package/lib/core/types/l10n.d.ts
CHANGED
|
@@ -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.welcomeText' | 'search.ai.newConversation' | 'search.ai.backToSearch' | 'search.ai.back' | 'search.ai.assistant' | 'search.ai.placeholder' | 'search.ai.generatingResponse' | 'search.ai.followUpQuestion' | '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.withArgs' | '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.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.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' | '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' | 'userMenu.login' | 'userMenu.logout' | 'userMenu.devOnboardingLabel' | 'mobileMenu.mainMenu' | 'mobileMenu.previous' | 'mobileMenu.products' | 'mobileMenu.version' | '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.copyConfig' | 'page.actions.connectMcp.copyConfigDescription' | '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.languages.moreButton.tooltipText' | '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.hideExample' | 'openapi.showExample' | 'openapi.expandAll' | 'openapi.collapseAll' | 'openapi.viewSecurityDetails' | 'openapi.noResponseExample' | 'openapi.discriminator.searchPlaceholder' | 'openapi.discriminator.searchNoResults' | 'openapi.discriminator.defaultMapping' | 'openapi.discriminator.defaultMappingTooltip' | 'openapi.noResponseContent' | 'openapi.noRequestPayload' | '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' | 'asyncapi.download.description.title' | 'asyncapi.info.title' | '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.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.nonNull' | 'graphql.required' | 'graphql.deprecated' | 'graphql.variables' | 'graphql.querySample' | 'graphql.mutationSample' | 'graphql.subscriptionSample' | 'graphql.responseSample' | 'graphql.locations' | 'graphql.sample' | 'graphql.referenced' | 'graphql.content.fragment' | 'button.copy.tooltipText' | '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';
|
|
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.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.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.withArgs' | '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.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.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' | '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' | 'userMenu.login' | 'userMenu.logout' | 'userMenu.devOnboardingLabel' | 'mobileMenu.mainMenu' | 'mobileMenu.previous' | 'mobileMenu.products' | 'mobileMenu.version' | '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.copyConfig' | 'page.actions.connectMcp.copyConfigDescription' | '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.languages.moreButton.tooltipText' | '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.hideExample' | 'openapi.showExample' | 'openapi.expandAll' | 'openapi.collapseAll' | 'openapi.viewSecurityDetails' | 'openapi.noResponseExample' | 'openapi.discriminator.searchPlaceholder' | 'openapi.discriminator.searchNoResults' | 'openapi.discriminator.defaultMapping' | 'openapi.discriminator.defaultMappingTooltip' | 'openapi.noResponseContent' | 'openapi.noRequestPayload' | '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' | 'asyncapi.download.description.title' | 'asyncapi.info.title' | '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.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.nonNull' | 'graphql.required' | 'graphql.deprecated' | 'graphql.variables' | 'graphql.querySample' | 'graphql.mutationSample' | 'graphql.subscriptionSample' | 'graphql.responseSample' | 'graphql.locations' | 'graphql.sample' | 'graphql.referenced' | 'graphql.content.fragment' | 'button.copy.tooltipText' | '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';
|
|
3
3
|
export type Locale = {
|
|
4
4
|
code: string;
|
|
5
5
|
name: string;
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import type { ReactNode } from 'react';
|
|
2
|
+
export type TooltipPlacement = 'top' | 'bottom' | 'left' | 'right';
|
|
2
3
|
export type TooltipProps = {
|
|
3
4
|
children?: ReactNode;
|
|
4
5
|
tip: string | ReactNode;
|
|
5
6
|
isOpen?: boolean;
|
|
6
7
|
withArrow?: boolean;
|
|
7
|
-
placement?:
|
|
8
|
+
placement?: TooltipPlacement;
|
|
9
|
+
fallbackPlacements?: TooltipPlacement[];
|
|
8
10
|
className?: string;
|
|
9
11
|
width?: string;
|
|
10
12
|
dataTestId?: string;
|
package/lib/core/utils/index.js
CHANGED
|
@@ -62,4 +62,5 @@ __exportStar(require("./build-revision-url"), exports);
|
|
|
62
62
|
__exportStar(require("./content-segments"), exports);
|
|
63
63
|
__exportStar(require("./custom-catalog-options-casing"), exports);
|
|
64
64
|
__exportStar(require("./get-auto-dismiss-duration"), exports);
|
|
65
|
+
__exportStar(require("./tooltip-placement"), exports);
|
|
65
66
|
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { TooltipPlacement, TooltipProps } from '../../core/types';
|
|
2
|
+
export declare function getDefaultFallbackPlacements(placement: TooltipPlacement): TooltipPlacement[];
|
|
3
|
+
export declare function calcAnchorPoint(triggerRect: DOMRect, placement: TooltipPlacement, arrowPosition: TooltipProps['arrowPosition']): {
|
|
4
|
+
top: number;
|
|
5
|
+
left: number;
|
|
6
|
+
};
|
|
7
|
+
type FitsInViewportParams = {
|
|
8
|
+
anchor: {
|
|
9
|
+
top: number;
|
|
10
|
+
left: number;
|
|
11
|
+
};
|
|
12
|
+
tooltipWidth: number;
|
|
13
|
+
tooltipHeight: number;
|
|
14
|
+
placement: TooltipPlacement;
|
|
15
|
+
arrowPosition: TooltipProps['arrowPosition'];
|
|
16
|
+
};
|
|
17
|
+
export declare function fitsInViewport({ anchor, tooltipWidth, tooltipHeight, placement, arrowPosition, }: FitsInViewportParams): boolean;
|
|
18
|
+
type ResolvePlacementParams = {
|
|
19
|
+
triggerRect: DOMRect;
|
|
20
|
+
tooltipWidth: number;
|
|
21
|
+
tooltipHeight: number;
|
|
22
|
+
placement: TooltipPlacement;
|
|
23
|
+
arrowPosition: TooltipProps['arrowPosition'];
|
|
24
|
+
fallbackPlacements: TooltipPlacement[] | undefined;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Given the trigger rect, tooltip dimensions, primary placement/arrow, and
|
|
28
|
+
* fallback list, returns the first placement that keeps the tooltip fully
|
|
29
|
+
* inside the viewport. Falls back to the primary when nothing fits.
|
|
30
|
+
*/
|
|
31
|
+
export declare function resolvePlacement({ triggerRect, tooltipWidth, tooltipHeight, placement, arrowPosition, fallbackPlacements, }: ResolvePlacementParams): TooltipPlacement;
|
|
32
|
+
export {};
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getDefaultFallbackPlacements = getDefaultFallbackPlacements;
|
|
4
|
+
exports.calcAnchorPoint = calcAnchorPoint;
|
|
5
|
+
exports.fitsInViewport = fitsInViewport;
|
|
6
|
+
exports.resolvePlacement = resolvePlacement;
|
|
7
|
+
const PLACEMENT_MARGIN = 10;
|
|
8
|
+
const COUNTER_CLOCKWISE = ['top', 'left', 'bottom', 'right'];
|
|
9
|
+
function getDefaultFallbackPlacements(placement) {
|
|
10
|
+
const index = COUNTER_CLOCKWISE.indexOf(placement);
|
|
11
|
+
const result = [];
|
|
12
|
+
for (let i = 1; i < COUNTER_CLOCKWISE.length; i++) {
|
|
13
|
+
result.push(COUNTER_CLOCKWISE[(index + i) % COUNTER_CLOCKWISE.length]);
|
|
14
|
+
}
|
|
15
|
+
return result;
|
|
16
|
+
}
|
|
17
|
+
function calcAnchorPoint(triggerRect, placement, arrowPosition) {
|
|
18
|
+
const horizontalLeft = () => arrowPosition === 'left'
|
|
19
|
+
? triggerRect.left - 24
|
|
20
|
+
: arrowPosition === 'right'
|
|
21
|
+
? triggerRect.right + 24
|
|
22
|
+
: triggerRect.left + triggerRect.width / 2;
|
|
23
|
+
const verticalTop = () => triggerRect.top + triggerRect.height / 2;
|
|
24
|
+
switch (placement) {
|
|
25
|
+
case 'top':
|
|
26
|
+
return { top: triggerRect.top, left: horizontalLeft() };
|
|
27
|
+
case 'bottom':
|
|
28
|
+
return { top: triggerRect.bottom, left: horizontalLeft() };
|
|
29
|
+
case 'left':
|
|
30
|
+
return { top: verticalTop(), left: triggerRect.left };
|
|
31
|
+
case 'right':
|
|
32
|
+
return { top: verticalTop(), left: triggerRect.right };
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function fitsInViewport({ anchor, tooltipWidth, tooltipHeight, placement, arrowPosition, }) {
|
|
36
|
+
const horizontalLeft = () => arrowPosition === 'left'
|
|
37
|
+
? anchor.left
|
|
38
|
+
: arrowPosition === 'right'
|
|
39
|
+
? anchor.left - tooltipWidth
|
|
40
|
+
: anchor.left - tooltipWidth / 2;
|
|
41
|
+
const verticalTop = () => anchor.top - tooltipHeight / 2;
|
|
42
|
+
let top;
|
|
43
|
+
let left;
|
|
44
|
+
switch (placement) {
|
|
45
|
+
case 'top':
|
|
46
|
+
top = anchor.top - tooltipHeight - PLACEMENT_MARGIN;
|
|
47
|
+
left = horizontalLeft();
|
|
48
|
+
break;
|
|
49
|
+
case 'bottom':
|
|
50
|
+
top = anchor.top + PLACEMENT_MARGIN;
|
|
51
|
+
left = horizontalLeft();
|
|
52
|
+
break;
|
|
53
|
+
case 'left':
|
|
54
|
+
top = verticalTop();
|
|
55
|
+
left = anchor.left - tooltipWidth - PLACEMENT_MARGIN;
|
|
56
|
+
break;
|
|
57
|
+
case 'right':
|
|
58
|
+
top = verticalTop();
|
|
59
|
+
left = anchor.left + PLACEMENT_MARGIN;
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
return (top >= 0 &&
|
|
63
|
+
left >= 0 &&
|
|
64
|
+
left + tooltipWidth <= window.innerWidth &&
|
|
65
|
+
top + tooltipHeight <= window.innerHeight);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Given the trigger rect, tooltip dimensions, primary placement/arrow, and
|
|
69
|
+
* fallback list, returns the first placement that keeps the tooltip fully
|
|
70
|
+
* inside the viewport. Falls back to the primary when nothing fits.
|
|
71
|
+
*/
|
|
72
|
+
function resolvePlacement({ triggerRect, tooltipWidth, tooltipHeight, placement, arrowPosition, fallbackPlacements, }) {
|
|
73
|
+
if (!(fallbackPlacements === null || fallbackPlacements === void 0 ? void 0 : fallbackPlacements.length) || tooltipWidth === 0 || tooltipHeight === 0) {
|
|
74
|
+
return placement;
|
|
75
|
+
}
|
|
76
|
+
const candidates = [placement, ...fallbackPlacements];
|
|
77
|
+
for (const candidate of candidates) {
|
|
78
|
+
const candidateArrow = candidate === placement ? arrowPosition : 'center';
|
|
79
|
+
const pos = calcAnchorPoint(triggerRect, candidate, candidateArrow);
|
|
80
|
+
if (fitsInViewport({
|
|
81
|
+
anchor: pos,
|
|
82
|
+
tooltipWidth,
|
|
83
|
+
tooltipHeight,
|
|
84
|
+
placement: candidate,
|
|
85
|
+
arrowPosition: candidateArrow,
|
|
86
|
+
})) {
|
|
87
|
+
return candidate;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return placement;
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=tooltip-placement.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@redocly/theme",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.65.0-next.1",
|
|
4
4
|
"description": "Shared UI components lib",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"theme",
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
"vitest": "4.0.10",
|
|
64
64
|
"vitest-when": "0.6.2",
|
|
65
65
|
"webpack": "5.105.2",
|
|
66
|
-
"@redocly/realm-asyncapi-sdk": "0.
|
|
66
|
+
"@redocly/realm-asyncapi-sdk": "0.11.0-next.1"
|
|
67
67
|
},
|
|
68
68
|
"dependencies": {
|
|
69
69
|
"@tanstack/react-query": "5.62.3",
|
|
@@ -81,7 +81,7 @@
|
|
|
81
81
|
"openapi-sampler": "^1.7.2",
|
|
82
82
|
"react-calendar": "5.1.0",
|
|
83
83
|
"react-date-picker": "11.0.0",
|
|
84
|
-
"@redocly/config": "0.48.
|
|
84
|
+
"@redocly/config": "0.48.1"
|
|
85
85
|
},
|
|
86
86
|
"scripts": {
|
|
87
87
|
"watch": "tsc -p tsconfig.build.json && (concurrently \"tsc -w -p tsconfig.build.json\" \"tsc-alias -w -p tsconfig.build.json\")",
|
|
@@ -4,7 +4,11 @@ import styled, { css } from 'styled-components';
|
|
|
4
4
|
import type { JSX, PropsWithChildren } from 'react';
|
|
5
5
|
import type { TooltipProps } from '@redocly/theme/core/types';
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
useControl,
|
|
9
|
+
useOutsideClick,
|
|
10
|
+
useTooltipFallbackPlacement,
|
|
11
|
+
} from '@redocly/theme/core/hooks';
|
|
8
12
|
import { Portal } from '@redocly/theme/components/Portal/Portal';
|
|
9
13
|
|
|
10
14
|
type Props = Exclude<TooltipProps, 'arrowPosition'> & {
|
|
@@ -17,6 +21,7 @@ function TooltipComponent({
|
|
|
17
21
|
tip,
|
|
18
22
|
withArrow = true,
|
|
19
23
|
placement = 'top',
|
|
24
|
+
fallbackPlacements,
|
|
20
25
|
className = 'default',
|
|
21
26
|
width,
|
|
22
27
|
dataTestId,
|
|
@@ -29,6 +34,14 @@ function TooltipComponent({
|
|
|
29
34
|
const { isOpened, handleOpen, handleClose } = useControl(isOpen);
|
|
30
35
|
const anchorName = `--tooltip${useId().replace(/:/g, '')}`;
|
|
31
36
|
|
|
37
|
+
const { activePlacement, activeArrowPosition } = useTooltipFallbackPlacement({
|
|
38
|
+
isOpened,
|
|
39
|
+
placement,
|
|
40
|
+
arrowPosition,
|
|
41
|
+
fallbackPlacements,
|
|
42
|
+
tooltipBodyRef,
|
|
43
|
+
});
|
|
44
|
+
|
|
32
45
|
useOutsideClick(isOpened ? [tooltipWrapperRef, tooltipBodyRef] : tooltipWrapperRef, handleClose);
|
|
33
46
|
|
|
34
47
|
const isControlled = isOpen !== undefined;
|
|
@@ -71,10 +84,14 @@ function TooltipComponent({
|
|
|
71
84
|
<TooltipBody
|
|
72
85
|
ref={tooltipBodyRef}
|
|
73
86
|
data-testid={dataTestId || (typeof tip === 'string' ? tip : '')}
|
|
74
|
-
placement={
|
|
87
|
+
placement={activePlacement}
|
|
75
88
|
width={width}
|
|
76
89
|
withArrow={withArrow}
|
|
77
|
-
arrowPosition={
|
|
90
|
+
arrowPosition={
|
|
91
|
+
activeArrowPosition === 'left' || activeArrowPosition === 'right'
|
|
92
|
+
? activeArrowPosition
|
|
93
|
+
: 'center'
|
|
94
|
+
}
|
|
78
95
|
anchorName={anchorName}
|
|
79
96
|
>
|
|
80
97
|
{tip}
|
|
@@ -6,6 +6,7 @@ import type { TooltipProps } from '@redocly/theme/core/types';
|
|
|
6
6
|
|
|
7
7
|
import { useControl, useOutsideClick } from '@redocly/theme/core/hooks';
|
|
8
8
|
import { Portal } from '@redocly/theme/components/Portal/Portal';
|
|
9
|
+
import { calcAnchorPoint, resolvePlacement } from '@redocly/theme/core/utils';
|
|
9
10
|
|
|
10
11
|
function TooltipComponent({
|
|
11
12
|
children,
|
|
@@ -13,6 +14,7 @@ function TooltipComponent({
|
|
|
13
14
|
tip,
|
|
14
15
|
withArrow = true,
|
|
15
16
|
placement = 'top',
|
|
17
|
+
fallbackPlacements,
|
|
16
18
|
className = 'default',
|
|
17
19
|
width,
|
|
18
20
|
dataTestId,
|
|
@@ -20,58 +22,41 @@ function TooltipComponent({
|
|
|
20
22
|
arrowPosition = 'center',
|
|
21
23
|
onClick,
|
|
22
24
|
}: PropsWithChildren<TooltipProps>): JSX.Element {
|
|
23
|
-
const
|
|
25
|
+
const wrapperRef = useRef<HTMLDivElement | null>(null);
|
|
26
|
+
const tooltipBodyRef = useRef<HTMLSpanElement | null>(null);
|
|
24
27
|
const { isOpened, handleOpen, handleClose } = useControl(isOpen);
|
|
25
28
|
const [tooltipPosition, setTooltipPosition] = useState({ top: 0, left: 0 });
|
|
29
|
+
const [activePlacement, setActivePlacement] = useState(placement);
|
|
26
30
|
|
|
27
|
-
|
|
31
|
+
const activeArrowPosition = activePlacement === placement ? arrowPosition : 'center';
|
|
32
|
+
|
|
33
|
+
useOutsideClick(wrapperRef, handleClose);
|
|
28
34
|
|
|
29
35
|
const isControlled = isOpen !== undefined;
|
|
30
36
|
|
|
31
|
-
const updateTooltipPosition = useCallback(() => {
|
|
32
|
-
if (isOpened
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
if (arrowPosition === 'left') {
|
|
52
|
-
left = rect.left - 24;
|
|
53
|
-
} else if (arrowPosition === 'right') {
|
|
54
|
-
left = rect.right + 24;
|
|
55
|
-
} else {
|
|
56
|
-
left = rect.left + rect.width / 2;
|
|
57
|
-
}
|
|
58
|
-
break;
|
|
59
|
-
case 'left':
|
|
60
|
-
top = rect.top + rect.height / 2;
|
|
61
|
-
left = rect.left;
|
|
62
|
-
break;
|
|
63
|
-
case 'right':
|
|
64
|
-
top = rect.top + rect.height / 2;
|
|
65
|
-
left = rect.right;
|
|
66
|
-
break;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
setTooltipPosition({ top, left });
|
|
70
|
-
}
|
|
71
|
-
}, [isOpened, placement, arrowPosition]);
|
|
37
|
+
const updateTooltipPosition = useCallback((): void => {
|
|
38
|
+
if (!isOpened || !wrapperRef.current) return;
|
|
39
|
+
|
|
40
|
+
const triggerRect = wrapperRef.current.getBoundingClientRect();
|
|
41
|
+
const tooltipWidth = tooltipBodyRef.current?.offsetWidth ?? 0;
|
|
42
|
+
const tooltipHeight = tooltipBodyRef.current?.offsetHeight ?? 0;
|
|
43
|
+
|
|
44
|
+
const resolved = resolvePlacement({
|
|
45
|
+
triggerRect,
|
|
46
|
+
tooltipWidth,
|
|
47
|
+
tooltipHeight,
|
|
48
|
+
placement,
|
|
49
|
+
arrowPosition,
|
|
50
|
+
fallbackPlacements,
|
|
51
|
+
});
|
|
52
|
+
const resolvedArrow = resolved === placement ? arrowPosition : 'center';
|
|
53
|
+
|
|
54
|
+
setTooltipPosition(calcAnchorPoint(triggerRect, resolved, resolvedArrow));
|
|
55
|
+
setActivePlacement(resolved);
|
|
56
|
+
}, [isOpened, placement, arrowPosition, fallbackPlacements]);
|
|
72
57
|
|
|
73
58
|
useLayoutEffect(() => {
|
|
74
|
-
if (isOpened &&
|
|
59
|
+
if (isOpened && wrapperRef.current) {
|
|
75
60
|
updateTooltipPosition();
|
|
76
61
|
|
|
77
62
|
const handleScroll = () => updateTooltipPosition();
|
|
@@ -109,7 +94,7 @@ function TooltipComponent({
|
|
|
109
94
|
|
|
110
95
|
return (
|
|
111
96
|
<TooltipWrapper
|
|
112
|
-
ref={
|
|
97
|
+
ref={wrapperRef}
|
|
113
98
|
{...controllers}
|
|
114
99
|
className={`tooltip-${className}`}
|
|
115
100
|
data-component-name="Tooltip/Tooltip"
|
|
@@ -118,11 +103,12 @@ function TooltipComponent({
|
|
|
118
103
|
{isOpened && !disabled && (
|
|
119
104
|
<Portal>
|
|
120
105
|
<TooltipBody
|
|
106
|
+
ref={tooltipBodyRef}
|
|
121
107
|
data-testid={dataTestId || (typeof tip === 'string' ? tip : '')}
|
|
122
|
-
placement={
|
|
108
|
+
placement={activePlacement}
|
|
123
109
|
width={width}
|
|
124
110
|
withArrow={withArrow}
|
|
125
|
-
arrowPosition={
|
|
111
|
+
arrowPosition={activeArrowPosition}
|
|
126
112
|
style={{
|
|
127
113
|
position: 'fixed',
|
|
128
114
|
top: tooltipPosition.top,
|
|
@@ -255,7 +241,9 @@ const TooltipWrapper = styled.div`
|
|
|
255
241
|
display: flex;
|
|
256
242
|
`;
|
|
257
243
|
const TooltipBody = styled.span<
|
|
258
|
-
Pick<Required<TooltipProps>, 'placement' | 'withArrow' | 'arrowPosition'> & {
|
|
244
|
+
Pick<Required<TooltipProps>, 'placement' | 'withArrow' | 'arrowPosition'> & {
|
|
245
|
+
width?: string;
|
|
246
|
+
}
|
|
259
247
|
>`
|
|
260
248
|
display: inline-block;
|
|
261
249
|
|
|
@@ -1,18 +1,29 @@
|
|
|
1
1
|
import React, { memo } from 'react';
|
|
2
2
|
|
|
3
|
+
import type { TooltipProps } from '@redocly/theme/core/types';
|
|
4
|
+
|
|
3
5
|
import { Tooltip as AnchorTooltip } from '@redocly/theme/components/Tooltip/AnchorTooltip';
|
|
4
6
|
import { Tooltip as JsTooltip } from '@redocly/theme/components/Tooltip/JsTooltip';
|
|
5
|
-
import { TooltipProps } from '@redocly/theme/core/types';
|
|
6
7
|
import { useThemeHooks } from '@redocly/theme/core/hooks';
|
|
8
|
+
import { getDefaultFallbackPlacements } from '@redocly/theme/core/utils';
|
|
7
9
|
|
|
8
10
|
function TooltipComponent(props: TooltipProps): React.ReactElement {
|
|
9
11
|
const { useAnchorPositioning } = useThemeHooks();
|
|
10
12
|
const { isSupported } = useAnchorPositioning();
|
|
11
13
|
|
|
14
|
+
const fallbackPlacements =
|
|
15
|
+
props.fallbackPlacements ?? getDefaultFallbackPlacements(props.placement ?? 'top');
|
|
16
|
+
|
|
12
17
|
if (isSupported) {
|
|
13
|
-
return
|
|
18
|
+
return (
|
|
19
|
+
<AnchorTooltip
|
|
20
|
+
{...props}
|
|
21
|
+
fallbackPlacements={fallbackPlacements}
|
|
22
|
+
arrowPosition={prepareArrowPosition(props.arrowPosition)}
|
|
23
|
+
/>
|
|
24
|
+
);
|
|
14
25
|
}
|
|
15
|
-
return <JsTooltip {...props} />;
|
|
26
|
+
return <JsTooltip {...props} fallbackPlacements={fallbackPlacements} />;
|
|
16
27
|
}
|
|
17
28
|
|
|
18
29
|
export const Tooltip = memo<TooltipProps>(TooltipComponent);
|
package/src/core/hooks/index.ts
CHANGED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { useLayoutEffect, useRef, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
import type { RefObject } from 'react';
|
|
4
|
+
import type { TooltipPlacement, TooltipProps } from '@redocly/theme/core/types';
|
|
5
|
+
|
|
6
|
+
type TooltipFallbackPlacementParams = {
|
|
7
|
+
isOpened: boolean;
|
|
8
|
+
placement: TooltipPlacement;
|
|
9
|
+
arrowPosition: TooltipProps['arrowPosition'];
|
|
10
|
+
fallbackPlacements: TooltipPlacement[] | undefined;
|
|
11
|
+
tooltipBodyRef: RefObject<HTMLElement | null>;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
type TooltipFallbackPlacementResult = {
|
|
15
|
+
activePlacement: TooltipPlacement;
|
|
16
|
+
activeArrowPosition: TooltipProps['arrowPosition'];
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export function useTooltipFallbackPlacement({
|
|
20
|
+
isOpened,
|
|
21
|
+
placement,
|
|
22
|
+
arrowPosition,
|
|
23
|
+
fallbackPlacements,
|
|
24
|
+
tooltipBodyRef,
|
|
25
|
+
}: TooltipFallbackPlacementParams): TooltipFallbackPlacementResult {
|
|
26
|
+
const [activePlacement, setActivePlacement] = useState<TooltipPlacement>(placement);
|
|
27
|
+
const wasOpenRef = useRef(false);
|
|
28
|
+
const candidateIndexRef = useRef(0);
|
|
29
|
+
|
|
30
|
+
useLayoutEffect(() => {
|
|
31
|
+
if (!isOpened) {
|
|
32
|
+
wasOpenRef.current = false;
|
|
33
|
+
candidateIndexRef.current = 0;
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (!wasOpenRef.current) {
|
|
38
|
+
wasOpenRef.current = true;
|
|
39
|
+
candidateIndexRef.current = 0;
|
|
40
|
+
if (activePlacement !== placement) {
|
|
41
|
+
setActivePlacement(placement);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (!tooltipBodyRef.current || !fallbackPlacements?.length) return;
|
|
47
|
+
|
|
48
|
+
const candidates: TooltipPlacement[] = [placement, ...fallbackPlacements];
|
|
49
|
+
|
|
50
|
+
if (candidateIndexRef.current >= candidates.length) return;
|
|
51
|
+
|
|
52
|
+
const rect = tooltipBodyRef.current.getBoundingClientRect();
|
|
53
|
+
const overflows =
|
|
54
|
+
rect.left < 0 ||
|
|
55
|
+
rect.top < 0 ||
|
|
56
|
+
rect.right > window.innerWidth ||
|
|
57
|
+
rect.bottom > window.innerHeight;
|
|
58
|
+
|
|
59
|
+
if (!overflows) return;
|
|
60
|
+
|
|
61
|
+
candidateIndexRef.current++;
|
|
62
|
+
|
|
63
|
+
if (candidateIndexRef.current < candidates.length) {
|
|
64
|
+
setActivePlacement(candidates[candidateIndexRef.current]);
|
|
65
|
+
} else if (activePlacement !== placement) {
|
|
66
|
+
setActivePlacement(placement);
|
|
67
|
+
}
|
|
68
|
+
}, [isOpened, activePlacement, placement, fallbackPlacements, tooltipBodyRef]);
|
|
69
|
+
|
|
70
|
+
const activeArrowPosition = activePlacement === placement ? arrowPosition : 'center';
|
|
71
|
+
|
|
72
|
+
return { activePlacement, activeArrowPosition };
|
|
73
|
+
}
|
|
@@ -1253,6 +1253,7 @@ const replay = css`
|
|
|
1253
1253
|
--replay-runtime-expression-color: rgba(54, 90, 249, 1); // @presenter Color
|
|
1254
1254
|
--replay-runtime-expression-bg-color: rgba(54, 90, 249, 0.08); // @presenter Color
|
|
1255
1255
|
--replay-operators-color: rgba(193, 142, 31, 1); // @presenter Color
|
|
1256
|
+
--replay-claude-icon-color: rgba(217, 119, 87, 1); // @presenter Color
|
|
1256
1257
|
|
|
1257
1258
|
--replay-ai-gradient-soft: linear-gradient(62.6deg, rgba(113, 94, 254, 0.16) 0%, rgba(255, 92, 220, 0.16) 100%);
|
|
1258
1259
|
--replay-ai-gradient-disabled: linear-gradient(62.6deg, rgba(113, 94, 254, 0.6) 0%, rgba(255, 92, 220, 0.6) 100%);
|
package/src/core/types/l10n.ts
CHANGED
|
@@ -18,6 +18,15 @@ export type TranslationKey =
|
|
|
18
18
|
| 'dev.edit.description.dialog.title'
|
|
19
19
|
| 'dev.edit.description.dialog.save'
|
|
20
20
|
| 'dev.edit.description.dialog.cancel'
|
|
21
|
+
| 'dev.create.app.dialog.callbackUrls'
|
|
22
|
+
| 'dev.create.app.dialog.callbackUrls.placeholder'
|
|
23
|
+
| 'dev.create.app.dialog.callbackUrls.hint'
|
|
24
|
+
| 'dev.app.callbackUrls.title'
|
|
25
|
+
| 'dev.edit.callbackUrls.dialog.title'
|
|
26
|
+
| 'dev.edit.callbackUrls.dialog.placeholder'
|
|
27
|
+
| 'dev.edit.callbackUrls.dialog.hint'
|
|
28
|
+
| 'dev.edit.callbackUrls.dialog.save'
|
|
29
|
+
| 'dev.edit.callbackUrls.dialog.cancel'
|
|
21
30
|
| 'dev.edit.apis.dialog.selectedAPIs'
|
|
22
31
|
| 'dev.app.key.create'
|
|
23
32
|
| 'dev.create.key.dialog.title'
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import type { ReactNode } from 'react';
|
|
2
2
|
|
|
3
|
+
export type TooltipPlacement = 'top' | 'bottom' | 'left' | 'right';
|
|
4
|
+
|
|
3
5
|
export type TooltipProps = {
|
|
4
6
|
children?: ReactNode;
|
|
5
7
|
tip: string | ReactNode;
|
|
6
8
|
isOpen?: boolean;
|
|
7
9
|
withArrow?: boolean;
|
|
8
|
-
placement?:
|
|
10
|
+
placement?: TooltipPlacement;
|
|
11
|
+
fallbackPlacements?: TooltipPlacement[];
|
|
9
12
|
className?: string;
|
|
10
13
|
width?: string;
|
|
11
14
|
dataTestId?: string;
|
package/src/core/utils/index.ts
CHANGED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import type { TooltipPlacement, TooltipProps } from '@redocly/theme/core/types';
|
|
2
|
+
|
|
3
|
+
const PLACEMENT_MARGIN = 10;
|
|
4
|
+
const COUNTER_CLOCKWISE: TooltipPlacement[] = ['top', 'left', 'bottom', 'right'];
|
|
5
|
+
|
|
6
|
+
export function getDefaultFallbackPlacements(placement: TooltipPlacement): TooltipPlacement[] {
|
|
7
|
+
const index = COUNTER_CLOCKWISE.indexOf(placement);
|
|
8
|
+
const result: TooltipPlacement[] = [];
|
|
9
|
+
for (let i = 1; i < COUNTER_CLOCKWISE.length; i++) {
|
|
10
|
+
result.push(COUNTER_CLOCKWISE[(index + i) % COUNTER_CLOCKWISE.length]);
|
|
11
|
+
}
|
|
12
|
+
return result;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function calcAnchorPoint(
|
|
16
|
+
triggerRect: DOMRect,
|
|
17
|
+
placement: TooltipPlacement,
|
|
18
|
+
arrowPosition: TooltipProps['arrowPosition'],
|
|
19
|
+
): { top: number; left: number } {
|
|
20
|
+
const horizontalLeft = (): number =>
|
|
21
|
+
arrowPosition === 'left'
|
|
22
|
+
? triggerRect.left - 24
|
|
23
|
+
: arrowPosition === 'right'
|
|
24
|
+
? triggerRect.right + 24
|
|
25
|
+
: triggerRect.left + triggerRect.width / 2;
|
|
26
|
+
|
|
27
|
+
const verticalTop = (): number => triggerRect.top + triggerRect.height / 2;
|
|
28
|
+
|
|
29
|
+
switch (placement) {
|
|
30
|
+
case 'top':
|
|
31
|
+
return { top: triggerRect.top, left: horizontalLeft() };
|
|
32
|
+
case 'bottom':
|
|
33
|
+
return { top: triggerRect.bottom, left: horizontalLeft() };
|
|
34
|
+
case 'left':
|
|
35
|
+
return { top: verticalTop(), left: triggerRect.left };
|
|
36
|
+
case 'right':
|
|
37
|
+
return { top: verticalTop(), left: triggerRect.right };
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
type FitsInViewportParams = {
|
|
42
|
+
anchor: { top: number; left: number };
|
|
43
|
+
tooltipWidth: number;
|
|
44
|
+
tooltipHeight: number;
|
|
45
|
+
placement: TooltipPlacement;
|
|
46
|
+
arrowPosition: TooltipProps['arrowPosition'];
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export function fitsInViewport({
|
|
50
|
+
anchor,
|
|
51
|
+
tooltipWidth,
|
|
52
|
+
tooltipHeight,
|
|
53
|
+
placement,
|
|
54
|
+
arrowPosition,
|
|
55
|
+
}: FitsInViewportParams): boolean {
|
|
56
|
+
const horizontalLeft = (): number =>
|
|
57
|
+
arrowPosition === 'left'
|
|
58
|
+
? anchor.left
|
|
59
|
+
: arrowPosition === 'right'
|
|
60
|
+
? anchor.left - tooltipWidth
|
|
61
|
+
: anchor.left - tooltipWidth / 2;
|
|
62
|
+
|
|
63
|
+
const verticalTop = (): number => anchor.top - tooltipHeight / 2;
|
|
64
|
+
|
|
65
|
+
let top: number;
|
|
66
|
+
let left: number;
|
|
67
|
+
|
|
68
|
+
switch (placement) {
|
|
69
|
+
case 'top':
|
|
70
|
+
top = anchor.top - tooltipHeight - PLACEMENT_MARGIN;
|
|
71
|
+
left = horizontalLeft();
|
|
72
|
+
break;
|
|
73
|
+
case 'bottom':
|
|
74
|
+
top = anchor.top + PLACEMENT_MARGIN;
|
|
75
|
+
left = horizontalLeft();
|
|
76
|
+
break;
|
|
77
|
+
case 'left':
|
|
78
|
+
top = verticalTop();
|
|
79
|
+
left = anchor.left - tooltipWidth - PLACEMENT_MARGIN;
|
|
80
|
+
break;
|
|
81
|
+
case 'right':
|
|
82
|
+
top = verticalTop();
|
|
83
|
+
left = anchor.left + PLACEMENT_MARGIN;
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return (
|
|
88
|
+
top >= 0 &&
|
|
89
|
+
left >= 0 &&
|
|
90
|
+
left + tooltipWidth <= window.innerWidth &&
|
|
91
|
+
top + tooltipHeight <= window.innerHeight
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
type ResolvePlacementParams = {
|
|
96
|
+
triggerRect: DOMRect;
|
|
97
|
+
tooltipWidth: number;
|
|
98
|
+
tooltipHeight: number;
|
|
99
|
+
placement: TooltipPlacement;
|
|
100
|
+
arrowPosition: TooltipProps['arrowPosition'];
|
|
101
|
+
fallbackPlacements: TooltipPlacement[] | undefined;
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Given the trigger rect, tooltip dimensions, primary placement/arrow, and
|
|
106
|
+
* fallback list, returns the first placement that keeps the tooltip fully
|
|
107
|
+
* inside the viewport. Falls back to the primary when nothing fits.
|
|
108
|
+
*/
|
|
109
|
+
export function resolvePlacement({
|
|
110
|
+
triggerRect,
|
|
111
|
+
tooltipWidth,
|
|
112
|
+
tooltipHeight,
|
|
113
|
+
placement,
|
|
114
|
+
arrowPosition,
|
|
115
|
+
fallbackPlacements,
|
|
116
|
+
}: ResolvePlacementParams): TooltipPlacement {
|
|
117
|
+
if (!fallbackPlacements?.length || tooltipWidth === 0 || tooltipHeight === 0) {
|
|
118
|
+
return placement;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const candidates: TooltipPlacement[] = [placement, ...fallbackPlacements];
|
|
122
|
+
|
|
123
|
+
for (const candidate of candidates) {
|
|
124
|
+
const candidateArrow = candidate === placement ? arrowPosition : 'center';
|
|
125
|
+
const pos = calcAnchorPoint(triggerRect, candidate, candidateArrow);
|
|
126
|
+
|
|
127
|
+
if (
|
|
128
|
+
fitsInViewport({
|
|
129
|
+
anchor: pos,
|
|
130
|
+
tooltipWidth,
|
|
131
|
+
tooltipHeight,
|
|
132
|
+
placement: candidate,
|
|
133
|
+
arrowPosition: candidateArrow,
|
|
134
|
+
})
|
|
135
|
+
) {
|
|
136
|
+
return candidate;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return placement;
|
|
141
|
+
}
|