@redocly/theme 0.62.0-next.4 → 0.62.0-next.5
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/SvgViewer/SvgViewer.d.ts +15 -0
- package/lib/components/SvgViewer/SvgViewer.js +312 -0
- package/lib/components/SvgViewer/variables.d.ts +1 -0
- package/lib/components/SvgViewer/variables.dark.d.ts +1 -0
- package/lib/components/SvgViewer/variables.dark.js +8 -0
- package/lib/components/SvgViewer/variables.js +17 -0
- package/lib/components/Tag/variables.dark.js +6 -0
- package/lib/components/Tag/variables.js +6 -0
- package/lib/core/styles/dark.js +2 -0
- package/lib/core/styles/global.js +2 -0
- package/lib/core/types/catalog.d.ts +1 -1
- package/lib/core/types/l10n.d.ts +1 -1
- package/lib/core/utils/transform-revisions-to-version-history.js +8 -51
- package/lib/icons/FitToViewIcon/FitToViewIcon.d.ts +9 -0
- package/lib/icons/FitToViewIcon/FitToViewIcon.js +25 -0
- package/lib/index.d.ts +2 -0
- package/lib/index.js +2 -0
- package/lib/markdoc/components/Mermaid/Mermaid.js +70 -2
- package/package.json +4 -4
- package/src/components/SvgViewer/SvgViewer.tsx +405 -0
- package/src/components/SvgViewer/variables.dark.ts +5 -0
- package/src/components/SvgViewer/variables.ts +14 -0
- package/src/components/Tag/variables.dark.ts +6 -0
- package/src/components/Tag/variables.ts +6 -0
- package/src/core/styles/dark.ts +2 -0
- package/src/core/styles/global.ts +2 -0
- package/src/core/types/catalog.ts +1 -1
- package/src/core/types/l10n.ts +7 -1
- package/src/core/utils/transform-revisions-to-version-history.ts +8 -80
- package/src/icons/FitToViewIcon/FitToViewIcon.tsx +26 -0
- package/src/index.ts +2 -0
- package/src/markdoc/components/Mermaid/Mermaid.tsx +57 -8
package/lib/index.d.ts
CHANGED
|
@@ -25,6 +25,7 @@ export * from './components/Tags/CounterTag';
|
|
|
25
25
|
export * from './components/VersionPicker/VersionPicker';
|
|
26
26
|
export * from './components/Marker/Marker';
|
|
27
27
|
export * from './components/PageActions/PageActions';
|
|
28
|
+
export * from './components/SvgViewer/SvgViewer';
|
|
28
29
|
export * from './components/Buttons/CopyButton';
|
|
29
30
|
export * from './components/Buttons/EditPageButton';
|
|
30
31
|
export * from './components/Buttons/EmailButton';
|
|
@@ -266,6 +267,7 @@ export * from './icons/WorkflowHierarchyIcon/WorkflowHierarchyIcon';
|
|
|
266
267
|
export * from './icons/GenericIcon/GenericIcon';
|
|
267
268
|
export * from './icons/ShareIcon/ShareIcon';
|
|
268
269
|
export * from './icons/HashtagIcon/HashtagIcon';
|
|
270
|
+
export * from './icons/FitToViewIcon/FitToViewIcon';
|
|
269
271
|
export * from './layouts/RootLayout';
|
|
270
272
|
export * from './layouts/PageLayout';
|
|
271
273
|
export * from './layouts/NotFound';
|
package/lib/index.js
CHANGED
|
@@ -64,6 +64,7 @@ __exportStar(require("./components/Tags/CounterTag"), exports);
|
|
|
64
64
|
__exportStar(require("./components/VersionPicker/VersionPicker"), exports);
|
|
65
65
|
__exportStar(require("./components/Marker/Marker"), exports);
|
|
66
66
|
__exportStar(require("./components/PageActions/PageActions"), exports);
|
|
67
|
+
__exportStar(require("./components/SvgViewer/SvgViewer"), exports);
|
|
67
68
|
/* Buttons */
|
|
68
69
|
__exportStar(require("./components/Buttons/CopyButton"), exports);
|
|
69
70
|
__exportStar(require("./components/Buttons/EditPageButton"), exports);
|
|
@@ -329,6 +330,7 @@ __exportStar(require("./icons/WorkflowHierarchyIcon/WorkflowHierarchyIcon"), exp
|
|
|
329
330
|
__exportStar(require("./icons/GenericIcon/GenericIcon"), exports);
|
|
330
331
|
__exportStar(require("./icons/ShareIcon/ShareIcon"), exports);
|
|
331
332
|
__exportStar(require("./icons/HashtagIcon/HashtagIcon"), exports);
|
|
333
|
+
__exportStar(require("./icons/FitToViewIcon/FitToViewIcon"), exports);
|
|
332
334
|
/* Layouts */
|
|
333
335
|
__exportStar(require("./layouts/RootLayout"), exports);
|
|
334
336
|
__exportStar(require("./layouts/PageLayout"), exports);
|
|
@@ -1,18 +1,75 @@
|
|
|
1
1
|
"use strict";
|
|
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
|
+
})();
|
|
2
35
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
37
|
};
|
|
5
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
39
|
exports.Mermaid = Mermaid;
|
|
7
|
-
const react_1 =
|
|
40
|
+
const react_1 = __importStar(require("react"));
|
|
8
41
|
const styled_components_1 = __importDefault(require("styled-components"));
|
|
9
42
|
const utils_1 = require("../../../core/utils");
|
|
43
|
+
const hooks_1 = require("../../../core/hooks");
|
|
44
|
+
const SvgViewer_1 = require("../../../components/SvgViewer/SvgViewer");
|
|
10
45
|
function Mermaid({ diagramHtml, 'data-source': dataSource, 'data-hash': dataHash, className, }) {
|
|
11
|
-
|
|
46
|
+
const { useTranslate } = (0, hooks_1.useThemeHooks)();
|
|
47
|
+
const { translate } = useTranslate();
|
|
48
|
+
const [isOpen, setIsOpen] = (0, react_1.useState)(false);
|
|
49
|
+
const open = () => setIsOpen(true);
|
|
50
|
+
const close = () => setIsOpen(false);
|
|
51
|
+
return (react_1.default.createElement(react_1.default.Fragment, null,
|
|
52
|
+
react_1.default.createElement(Wrapper, { className: (0, utils_1.concatClassNames)('mermaid-wrapper', className), dangerouslySetInnerHTML: { __html: diagramHtml }, "data-component-name": "Markdoc/Mermaid/Mermaid", "data-source": dataSource, "data-hash": dataHash, onClick: open, onKeyDown: (e) => e.key === 'Enter' || (e.key === ' ' && open()), role: "button", tabIndex: 0, "aria-label": translate('mermaid.openFullscreen', 'Click to open diagram in fullscreen') }),
|
|
53
|
+
react_1.default.createElement(SvgViewer_1.SvgViewer, { isOpen: isOpen, onClose: close, labels: {
|
|
54
|
+
zoomIn: translate('mermaid.zoomIn', 'Zoom in'),
|
|
55
|
+
zoomOut: translate('mermaid.zoomOut', 'Zoom out'),
|
|
56
|
+
fitToView: translate('mermaid.reset', 'Fit to view'),
|
|
57
|
+
close: translate('mermaid.close', 'Close'),
|
|
58
|
+
dialogLabel: translate('mermaid.viewer', 'Mermaid diagram viewer'),
|
|
59
|
+
} },
|
|
60
|
+
react_1.default.createElement(ViewerContent, { dangerouslySetInnerHTML: { __html: diagramHtml } }))));
|
|
12
61
|
}
|
|
13
62
|
const Wrapper = styled_components_1.default.div `
|
|
14
63
|
background-color: var(--mermaid-bg-color);
|
|
15
64
|
border-radius: var(--mermaid-border-radius);
|
|
65
|
+
cursor: pointer;
|
|
66
|
+
transition: box-shadow 0.2s ease;
|
|
67
|
+
|
|
68
|
+
&:hover,
|
|
69
|
+
&:focus {
|
|
70
|
+
outline: none;
|
|
71
|
+
box-shadow: 0 0 0 2px var(--border-color-input-focus);
|
|
72
|
+
}
|
|
16
73
|
|
|
17
74
|
* {
|
|
18
75
|
font-family: var(--mermaid-font-family) !important;
|
|
@@ -23,4 +80,15 @@ const Wrapper = styled_components_1.default.div `
|
|
|
23
80
|
max-width: 100%;
|
|
24
81
|
}
|
|
25
82
|
`;
|
|
83
|
+
const ViewerContent = styled_components_1.default.div `
|
|
84
|
+
* {
|
|
85
|
+
font-family: var(--mermaid-font-family) !important;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.mermaid > svg {
|
|
89
|
+
font-size: var(--font-size-base) !important;
|
|
90
|
+
display: block;
|
|
91
|
+
max-width: none !important;
|
|
92
|
+
}
|
|
93
|
+
`;
|
|
26
94
|
//# sourceMappingURL=Mermaid.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@redocly/theme",
|
|
3
|
-
"version": "0.62.0-next.
|
|
3
|
+
"version": "0.62.0-next.5",
|
|
4
4
|
"description": "Shared UI components lib",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"theme",
|
|
@@ -29,8 +29,8 @@
|
|
|
29
29
|
"@markdoc/markdoc": "0.5.2",
|
|
30
30
|
"lodash.debounce": "^4.0.8",
|
|
31
31
|
"lodash.throttle": "^4.1.1",
|
|
32
|
-
"react": "19.2.
|
|
33
|
-
"react-dom": "19.2.
|
|
32
|
+
"react": "19.2.4",
|
|
33
|
+
"react-dom": "19.2.4",
|
|
34
34
|
"react-router-dom": "^6.21.1",
|
|
35
35
|
"styled-components": "^4.1.1 || ^5.3.11 || ^6.0.0"
|
|
36
36
|
},
|
|
@@ -81,7 +81,7 @@
|
|
|
81
81
|
"openapi-sampler": "1.6.2",
|
|
82
82
|
"react-calendar": "5.1.0",
|
|
83
83
|
"react-date-picker": "11.0.0",
|
|
84
|
-
"@redocly/config": "0.41.
|
|
84
|
+
"@redocly/config": "0.41.4"
|
|
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\")",
|
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
import React, { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';
|
|
2
|
+
import styled, { keyframes } from 'styled-components';
|
|
3
|
+
|
|
4
|
+
import type { JSX, TouchEvent as ReactTouchEvent, WheelEvent, MouseEvent, ReactNode } from 'react';
|
|
5
|
+
|
|
6
|
+
import { useModalScrollLock } from '@redocly/theme/core/hooks';
|
|
7
|
+
import { Button } from '@redocly/theme/components/Button/Button';
|
|
8
|
+
import { Tooltip } from '@redocly/theme/components/Tooltip/Tooltip';
|
|
9
|
+
import { AddIcon } from '@redocly/theme/icons/AddIcon/AddIcon';
|
|
10
|
+
import { SubtractIcon } from '@redocly/theme/icons/SubtractIcon/SubtractIcon';
|
|
11
|
+
import { CloseIcon } from '@redocly/theme/icons/CloseIcon/CloseIcon';
|
|
12
|
+
import { FitToViewIcon } from '@redocly/theme/icons/FitToViewIcon/FitToViewIcon';
|
|
13
|
+
|
|
14
|
+
export type SvgViewerLabels = {
|
|
15
|
+
zoomIn?: string;
|
|
16
|
+
zoomOut?: string;
|
|
17
|
+
fitToView?: string;
|
|
18
|
+
close?: string;
|
|
19
|
+
dialogLabel?: string;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export type SvgViewerProps = {
|
|
23
|
+
isOpen: boolean;
|
|
24
|
+
onClose: () => void;
|
|
25
|
+
children: ReactNode;
|
|
26
|
+
labels?: SvgViewerLabels;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
type Position = { x: number; y: number };
|
|
30
|
+
|
|
31
|
+
const MIN_SCALE_FACTOR = 0.1;
|
|
32
|
+
const MAX_SCALE_FACTOR = 5;
|
|
33
|
+
const ZOOM_STEP = 0.1;
|
|
34
|
+
const WHEEL_SENSITIVITY = 0.002;
|
|
35
|
+
const VIEWPORT_PADDING = 60;
|
|
36
|
+
const FIT_SCALE_FACTOR = 0.9;
|
|
37
|
+
|
|
38
|
+
export function SvgViewer({
|
|
39
|
+
isOpen,
|
|
40
|
+
onClose,
|
|
41
|
+
children,
|
|
42
|
+
labels = {},
|
|
43
|
+
}: SvgViewerProps): JSX.Element | null {
|
|
44
|
+
const [scale, setScale] = useState(1);
|
|
45
|
+
const [baseScale, setBaseScale] = useState(1);
|
|
46
|
+
const [position, setPosition] = useState<Position>({ x: 0, y: 0 });
|
|
47
|
+
const [isDragging, setIsDragging] = useState(false);
|
|
48
|
+
const [dragStart, setDragStart] = useState<Position>({ x: 0, y: 0 });
|
|
49
|
+
const [pinchState, setPinchState] = useState<{ distance: number; scale: number } | null>(null);
|
|
50
|
+
const [isWheelZooming, setIsWheelZooming] = useState(false);
|
|
51
|
+
|
|
52
|
+
const wheelTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
53
|
+
const overlayRef = useRef<HTMLDivElement>(null);
|
|
54
|
+
const viewportRef = useRef<HTMLDivElement>(null);
|
|
55
|
+
const contentRef = useRef<HTMLDivElement>(null);
|
|
56
|
+
const renderedScaleRef = useRef(scale);
|
|
57
|
+
|
|
58
|
+
useModalScrollLock(isOpen);
|
|
59
|
+
|
|
60
|
+
// Keep track of the actually rendered scale for accurate measurements
|
|
61
|
+
useLayoutEffect(() => {
|
|
62
|
+
renderedScaleRef.current = scale;
|
|
63
|
+
}, [scale]);
|
|
64
|
+
|
|
65
|
+
const minScale = baseScale * MIN_SCALE_FACTOR;
|
|
66
|
+
const maxScale = baseScale * MAX_SCALE_FACTOR;
|
|
67
|
+
|
|
68
|
+
const clampScale = useCallback(
|
|
69
|
+
(value: number) => Math.min(maxScale, Math.max(minScale, value)),
|
|
70
|
+
[minScale, maxScale],
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
const calculateFitScale = useCallback(() => {
|
|
74
|
+
if (!viewportRef.current || !contentRef.current) return 1;
|
|
75
|
+
|
|
76
|
+
const viewport = viewportRef.current.getBoundingClientRect();
|
|
77
|
+
const svg = contentRef.current.querySelector('svg');
|
|
78
|
+
if (!svg) return 1;
|
|
79
|
+
|
|
80
|
+
const svgRect = svg.getBoundingClientRect();
|
|
81
|
+
if (!svgRect.width || !svgRect.height) return 1;
|
|
82
|
+
|
|
83
|
+
// getBoundingClientRect returns transformed size, so compensate for current scale
|
|
84
|
+
const currentScale = renderedScaleRef.current || 1;
|
|
85
|
+
const naturalWidth = svgRect.width / currentScale;
|
|
86
|
+
const naturalHeight = svgRect.height / currentScale;
|
|
87
|
+
|
|
88
|
+
const availableWidth = viewport.width - VIEWPORT_PADDING * 2;
|
|
89
|
+
const availableHeight = viewport.height - VIEWPORT_PADDING * 2;
|
|
90
|
+
|
|
91
|
+
return (
|
|
92
|
+
Math.min(availableWidth / naturalWidth, availableHeight / naturalHeight) * FIT_SCALE_FACTOR
|
|
93
|
+
);
|
|
94
|
+
}, []);
|
|
95
|
+
|
|
96
|
+
const resetView = useCallback(() => {
|
|
97
|
+
setScale(baseScale);
|
|
98
|
+
setPosition({ x: 0, y: 0 });
|
|
99
|
+
}, [baseScale]);
|
|
100
|
+
|
|
101
|
+
const zoomIn = useCallback(() => {
|
|
102
|
+
setScale((s) => clampScale(s + baseScale * ZOOM_STEP));
|
|
103
|
+
}, [baseScale, clampScale]);
|
|
104
|
+
|
|
105
|
+
const zoomOut = useCallback(() => {
|
|
106
|
+
setScale((s) => clampScale(s - baseScale * ZOOM_STEP));
|
|
107
|
+
}, [baseScale, clampScale]);
|
|
108
|
+
|
|
109
|
+
const handleKeyDown = useCallback(
|
|
110
|
+
(e: React.KeyboardEvent) => {
|
|
111
|
+
switch (e.key) {
|
|
112
|
+
case 'Escape':
|
|
113
|
+
onClose();
|
|
114
|
+
break;
|
|
115
|
+
case '+':
|
|
116
|
+
case '=':
|
|
117
|
+
zoomIn();
|
|
118
|
+
break;
|
|
119
|
+
case '-':
|
|
120
|
+
zoomOut();
|
|
121
|
+
break;
|
|
122
|
+
case '0':
|
|
123
|
+
resetView();
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
[onClose, zoomIn, zoomOut, resetView],
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
const handleWheel = useCallback(
|
|
131
|
+
(e: WheelEvent) => {
|
|
132
|
+
e.preventDefault();
|
|
133
|
+
e.stopPropagation();
|
|
134
|
+
|
|
135
|
+
setIsWheelZooming(true);
|
|
136
|
+
if (wheelTimeoutRef.current) clearTimeout(wheelTimeoutRef.current);
|
|
137
|
+
wheelTimeoutRef.current = setTimeout(() => setIsWheelZooming(false), 150);
|
|
138
|
+
|
|
139
|
+
const delta = -e.deltaY * WHEEL_SENSITIVITY;
|
|
140
|
+
setScale((s) => clampScale(s + s * delta));
|
|
141
|
+
},
|
|
142
|
+
[clampScale],
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
const handleMouseDown = useCallback(
|
|
146
|
+
(e: MouseEvent) => {
|
|
147
|
+
if (e.button !== 0) return;
|
|
148
|
+
e.preventDefault();
|
|
149
|
+
setIsDragging(true);
|
|
150
|
+
setDragStart({ x: e.clientX - position.x, y: e.clientY - position.y });
|
|
151
|
+
},
|
|
152
|
+
[position],
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
const handleMouseMove = useCallback(
|
|
156
|
+
(e: MouseEvent) => {
|
|
157
|
+
if (!isDragging) return;
|
|
158
|
+
setPosition({ x: e.clientX - dragStart.x, y: e.clientY - dragStart.y });
|
|
159
|
+
},
|
|
160
|
+
[isDragging, dragStart],
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
const handleMouseUp = useCallback(() => setIsDragging(false), []);
|
|
164
|
+
|
|
165
|
+
const getTouchDistance = (touches: React.TouchList): number => {
|
|
166
|
+
if (touches.length !== 2) return 0;
|
|
167
|
+
const dx = touches[0].clientX - touches[1].clientX;
|
|
168
|
+
const dy = touches[0].clientY - touches[1].clientY;
|
|
169
|
+
return Math.hypot(dx, dy);
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
const handleTouchStart = useCallback(
|
|
173
|
+
(e: ReactTouchEvent) => {
|
|
174
|
+
if (e.touches.length === 2) {
|
|
175
|
+
setPinchState({ distance: getTouchDistance(e.touches), scale });
|
|
176
|
+
} else if (e.touches.length === 1) {
|
|
177
|
+
setIsDragging(true);
|
|
178
|
+
setDragStart({
|
|
179
|
+
x: e.touches[0].clientX - position.x,
|
|
180
|
+
y: e.touches[0].clientY - position.y,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
},
|
|
184
|
+
[position, scale],
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
const handleTouchMove = useCallback(
|
|
188
|
+
(e: ReactTouchEvent) => {
|
|
189
|
+
e.preventDefault();
|
|
190
|
+
if (e.touches.length === 2 && pinchState) {
|
|
191
|
+
const distance = getTouchDistance(e.touches);
|
|
192
|
+
setScale(clampScale(pinchState.scale * (distance / pinchState.distance)));
|
|
193
|
+
} else if (e.touches.length === 1 && isDragging) {
|
|
194
|
+
setPosition({
|
|
195
|
+
x: e.touches[0].clientX - dragStart.x,
|
|
196
|
+
y: e.touches[0].clientY - dragStart.y,
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
},
|
|
200
|
+
[pinchState, isDragging, dragStart, clampScale],
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
const handleTouchEnd = useCallback(() => {
|
|
204
|
+
setIsDragging(false);
|
|
205
|
+
setPinchState(null);
|
|
206
|
+
}, []);
|
|
207
|
+
|
|
208
|
+
useEffect(() => {
|
|
209
|
+
if (!isOpen) return;
|
|
210
|
+
|
|
211
|
+
setPosition({ x: 0, y: 0 });
|
|
212
|
+
overlayRef.current?.focus();
|
|
213
|
+
|
|
214
|
+
// Wait for DOM to be ready before measuring
|
|
215
|
+
requestAnimationFrame(() => {
|
|
216
|
+
const fitScale = calculateFitScale();
|
|
217
|
+
setBaseScale(fitScale);
|
|
218
|
+
setScale(fitScale);
|
|
219
|
+
});
|
|
220
|
+
}, [isOpen, calculateFitScale]);
|
|
221
|
+
|
|
222
|
+
if (!isOpen) return null;
|
|
223
|
+
|
|
224
|
+
const zoomPercentage = baseScale > 0 ? Math.round((scale / baseScale) * 100) : 100;
|
|
225
|
+
const isAnimating = !isDragging && !isWheelZooming && !pinchState;
|
|
226
|
+
|
|
227
|
+
return (
|
|
228
|
+
<Overlay
|
|
229
|
+
ref={overlayRef}
|
|
230
|
+
onClick={onClose}
|
|
231
|
+
onKeyDown={handleKeyDown}
|
|
232
|
+
tabIndex={0}
|
|
233
|
+
aria-modal="true"
|
|
234
|
+
role="dialog"
|
|
235
|
+
aria-label={labels.dialogLabel || 'SVG viewer'}
|
|
236
|
+
>
|
|
237
|
+
<Viewport
|
|
238
|
+
ref={viewportRef}
|
|
239
|
+
onClick={(e) => e.stopPropagation()}
|
|
240
|
+
onWheel={handleWheel}
|
|
241
|
+
onMouseDown={handleMouseDown}
|
|
242
|
+
onMouseMove={handleMouseMove}
|
|
243
|
+
onMouseUp={handleMouseUp}
|
|
244
|
+
onMouseLeave={handleMouseUp}
|
|
245
|
+
onTouchStart={handleTouchStart}
|
|
246
|
+
onTouchMove={handleTouchMove}
|
|
247
|
+
onTouchEnd={handleTouchEnd}
|
|
248
|
+
$isDragging={isDragging}
|
|
249
|
+
>
|
|
250
|
+
<Content
|
|
251
|
+
ref={contentRef}
|
|
252
|
+
$isAnimating={isAnimating}
|
|
253
|
+
style={{
|
|
254
|
+
transform: `translate(calc(-50% + ${position.x}px), calc(-50% + ${position.y}px)) scale(${scale})`,
|
|
255
|
+
}}
|
|
256
|
+
>
|
|
257
|
+
{children}
|
|
258
|
+
</Content>
|
|
259
|
+
<Controls>
|
|
260
|
+
<ControlGroup>
|
|
261
|
+
<Tooltip tip={labels.zoomOut || 'Zoom out'} placement="top">
|
|
262
|
+
<ControlButton
|
|
263
|
+
variant="text"
|
|
264
|
+
size="small"
|
|
265
|
+
icon={<SubtractIcon />}
|
|
266
|
+
onClick={zoomOut}
|
|
267
|
+
disabled={scale <= minScale}
|
|
268
|
+
/>
|
|
269
|
+
</Tooltip>
|
|
270
|
+
<ZoomLabel>{zoomPercentage}%</ZoomLabel>
|
|
271
|
+
<Tooltip tip={labels.zoomIn || 'Zoom in'} placement="top">
|
|
272
|
+
<ControlButton
|
|
273
|
+
variant="text"
|
|
274
|
+
size="small"
|
|
275
|
+
icon={<AddIcon />}
|
|
276
|
+
onClick={zoomIn}
|
|
277
|
+
disabled={scale >= maxScale}
|
|
278
|
+
/>
|
|
279
|
+
</Tooltip>
|
|
280
|
+
<Divider />
|
|
281
|
+
<Tooltip tip={labels.fitToView || 'Fit to view'} placement="top">
|
|
282
|
+
<ControlButton
|
|
283
|
+
variant="text"
|
|
284
|
+
size="small"
|
|
285
|
+
icon={<FitToViewIcon />}
|
|
286
|
+
onClick={resetView}
|
|
287
|
+
/>
|
|
288
|
+
</Tooltip>
|
|
289
|
+
<Tooltip tip={labels.close || 'Close'} placement="top">
|
|
290
|
+
<ControlButton variant="text" size="small" icon={<CloseIcon />} onClick={onClose} />
|
|
291
|
+
</Tooltip>
|
|
292
|
+
</ControlGroup>
|
|
293
|
+
</Controls>
|
|
294
|
+
</Viewport>
|
|
295
|
+
</Overlay>
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const scaleIn = keyframes`
|
|
300
|
+
from {
|
|
301
|
+
transform: scale(0.9);
|
|
302
|
+
}
|
|
303
|
+
to {
|
|
304
|
+
transform: scale(1);
|
|
305
|
+
}
|
|
306
|
+
`;
|
|
307
|
+
|
|
308
|
+
const slideUp = keyframes`
|
|
309
|
+
from {
|
|
310
|
+
opacity: 0;
|
|
311
|
+
transform: translateX(-50%) translateY(10px);
|
|
312
|
+
}
|
|
313
|
+
to {
|
|
314
|
+
opacity: 1;
|
|
315
|
+
transform: translateX(-50%) translateY(0);
|
|
316
|
+
}
|
|
317
|
+
`;
|
|
318
|
+
|
|
319
|
+
const Overlay = styled.div`
|
|
320
|
+
position: fixed;
|
|
321
|
+
inset: 0;
|
|
322
|
+
background-color: var(--svg-viewer-overlay-bg-color);
|
|
323
|
+
backdrop-filter: blur(var(--spacing-unit));
|
|
324
|
+
z-index: var(--z-index-overlay, 1000);
|
|
325
|
+
display: flex;
|
|
326
|
+
align-items: center;
|
|
327
|
+
justify-content: center;
|
|
328
|
+
padding: var(--spacing-xxl);
|
|
329
|
+
|
|
330
|
+
&:focus {
|
|
331
|
+
outline: none;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
@media (max-width: 768px) {
|
|
335
|
+
padding: var(--spacing-md);
|
|
336
|
+
}
|
|
337
|
+
`;
|
|
338
|
+
|
|
339
|
+
const Viewport = styled.div<{ $isDragging: boolean }>`
|
|
340
|
+
position: relative;
|
|
341
|
+
width: 100%;
|
|
342
|
+
height: 100%;
|
|
343
|
+
background-color: var(--svg-viewer-bg-color);
|
|
344
|
+
border-radius: var(--svg-viewer-border-radius);
|
|
345
|
+
overflow: hidden;
|
|
346
|
+
cursor: ${({ $isDragging }) => ($isDragging ? 'grabbing' : 'grab')};
|
|
347
|
+
touch-action: none;
|
|
348
|
+
box-shadow: var(--svg-viewer-box-shadow);
|
|
349
|
+
animation: ${scaleIn} 0.25s ease-in-out forwards;
|
|
350
|
+
`;
|
|
351
|
+
|
|
352
|
+
const Content = styled.div<{ $isAnimating: boolean }>`
|
|
353
|
+
position: absolute;
|
|
354
|
+
top: 50%;
|
|
355
|
+
left: 50%;
|
|
356
|
+
transform-origin: center center;
|
|
357
|
+
user-select: none;
|
|
358
|
+
pointer-events: none;
|
|
359
|
+
transition: ${({ $isAnimating }) => ($isAnimating ? 'transform 0.25s ease-in-out' : 'none')};
|
|
360
|
+
|
|
361
|
+
svg {
|
|
362
|
+
display: block;
|
|
363
|
+
max-width: none !important;
|
|
364
|
+
}
|
|
365
|
+
`;
|
|
366
|
+
|
|
367
|
+
const Controls = styled.div`
|
|
368
|
+
position: absolute;
|
|
369
|
+
bottom: var(--spacing-sm);
|
|
370
|
+
left: 50%;
|
|
371
|
+
transform: translateX(-50%);
|
|
372
|
+
z-index: 10;
|
|
373
|
+
animation: ${slideUp} 0.3s ease-out 0.1s backwards;
|
|
374
|
+
`;
|
|
375
|
+
|
|
376
|
+
const ControlGroup = styled.div`
|
|
377
|
+
display: flex;
|
|
378
|
+
align-items: center;
|
|
379
|
+
gap: 2px;
|
|
380
|
+
padding: var(--spacing-xxs);
|
|
381
|
+
background: var(--bg-color-raised);
|
|
382
|
+
border: 1px solid var(--border-color-primary);
|
|
383
|
+
border-radius: var(--border-radius-lg);
|
|
384
|
+
box-shadow: var(--bg-raised-shadow);
|
|
385
|
+
`;
|
|
386
|
+
|
|
387
|
+
const ControlButton = styled(Button)`
|
|
388
|
+
--button-icon-size: 16px;
|
|
389
|
+
`;
|
|
390
|
+
|
|
391
|
+
const ZoomLabel = styled.span`
|
|
392
|
+
min-width: 40px;
|
|
393
|
+
font-size: var(--font-size-sm);
|
|
394
|
+
font-weight: var(--font-weight-semibold);
|
|
395
|
+
color: var(--text-color-secondary);
|
|
396
|
+
text-align: center;
|
|
397
|
+
font-variant-numeric: tabular-nums;
|
|
398
|
+
`;
|
|
399
|
+
|
|
400
|
+
const Divider = styled.div`
|
|
401
|
+
width: 1px;
|
|
402
|
+
height: var(--spacing-base);
|
|
403
|
+
background: var(--border-color-primary);
|
|
404
|
+
margin: 0 var(--spacing-xxs);
|
|
405
|
+
`;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { css } from 'styled-components';
|
|
2
|
+
|
|
3
|
+
export const svgViewer = css`
|
|
4
|
+
/**
|
|
5
|
+
* @tokens SVG Viewer
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
--svg-viewer-overlay-bg-color: var(--bg-color-modal-overlay); // @presenter Color
|
|
9
|
+
--svg-viewer-bg-color: var(--bg-color); // @presenter Color
|
|
10
|
+
--svg-viewer-border-radius: var(--border-radius-xl); // @presenter BorderRadius
|
|
11
|
+
--svg-viewer-box-shadow: var(--bg-raised-shadow); // @presenter BoxShadow
|
|
12
|
+
|
|
13
|
+
// @tokens End
|
|
14
|
+
`;
|
|
@@ -182,6 +182,12 @@ export const tagDarkMode = css`
|
|
|
182
182
|
--tag-bg-color-hover: #3A465F; // @presenter Color
|
|
183
183
|
}
|
|
184
184
|
|
|
185
|
+
.tag-query {
|
|
186
|
+
--tag-color: #68cc97; // @presenter Color
|
|
187
|
+
--tag-bg-color: #1F3D2D; // @presenter Color
|
|
188
|
+
--tag-bg-color-hover: #34654B; // @presenter Color
|
|
189
|
+
}
|
|
190
|
+
|
|
185
191
|
.tag-put {
|
|
186
192
|
--tag-color: #e0a663; // @presenter Color
|
|
187
193
|
--tag-bg-color: #3D2D1B; // @presenter Color
|
|
@@ -295,6 +295,12 @@ export const tag = css`
|
|
|
295
295
|
--tag-bg-color-hover: #CEDDFD; // @presenter Color
|
|
296
296
|
}
|
|
297
297
|
|
|
298
|
+
.tag-query {
|
|
299
|
+
--tag-color: #25b869; // @presenter Color
|
|
300
|
+
--tag-bg-color: #e5faef; // @presenter Color
|
|
301
|
+
--tag-bg-color-hover: #D4F7E5; // @presenter Color
|
|
302
|
+
}
|
|
303
|
+
|
|
298
304
|
.tag-put {
|
|
299
305
|
--tag-color: #f5901d; // @presenter Color
|
|
300
306
|
--tag-bg-color: #fef1e2; // @presenter Color
|
package/src/core/styles/dark.ts
CHANGED
|
@@ -16,6 +16,7 @@ import { pageActionsDarkMode } from '@redocly/theme/components/PageActions/varia
|
|
|
16
16
|
import { tooltipDarkMode } from '@redocly/theme/components/Tooltip/variables.dark';
|
|
17
17
|
import { bannerDarkMode } from '@redocly/theme/components/Banner/variables.dark';
|
|
18
18
|
import { admonitionDarkMode } from '@redocly/theme/components/Admonition/variables.dark';
|
|
19
|
+
import { svgViewerDarkMode } from '@redocly/theme/components/SvgViewer/variables.dark';
|
|
19
20
|
|
|
20
21
|
const replayDarkMode = css`
|
|
21
22
|
/**
|
|
@@ -332,6 +333,7 @@ export const darkMode = css`
|
|
|
332
333
|
${tooltipDarkMode}
|
|
333
334
|
${bannerDarkMode}
|
|
334
335
|
${admonitionDarkMode}
|
|
336
|
+
${svgViewerDarkMode}
|
|
335
337
|
|
|
336
338
|
/**
|
|
337
339
|
* @tokens Dark Theme Scrollbar Config
|
|
@@ -42,6 +42,7 @@ import { cards } from '@redocly/theme/markdoc/components/Cards/variables';
|
|
|
42
42
|
import { codeWalkthrough } from '@redocly/theme/markdoc/components/CodeWalkthrough/variables';
|
|
43
43
|
import { skipContent } from '@redocly/theme/components/SkipContent/variables';
|
|
44
44
|
import { pageActions } from '@redocly/theme/components/PageActions/variables';
|
|
45
|
+
import { svgViewer } from '@redocly/theme/components/SvgViewer/variables';
|
|
45
46
|
|
|
46
47
|
import { darkMode } from './dark';
|
|
47
48
|
|
|
@@ -1319,6 +1320,7 @@ export const styles = css`
|
|
|
1319
1320
|
${replay}
|
|
1320
1321
|
${skipContent}
|
|
1321
1322
|
${pageActions}
|
|
1323
|
+
${svgViewer}
|
|
1322
1324
|
|
|
1323
1325
|
background-color: var(--bg-color);
|
|
1324
1326
|
color: var(--text-color-primary);
|
|
@@ -154,7 +154,7 @@ export type BffCatalogRelatedEntity = {
|
|
|
154
154
|
key: string;
|
|
155
155
|
title: string;
|
|
156
156
|
summary?: string | null;
|
|
157
|
-
readonly source: '
|
|
157
|
+
readonly source: 'remote' | 'file';
|
|
158
158
|
relationRole: 'source' | 'target';
|
|
159
159
|
relationType: EntityRelationType;
|
|
160
160
|
sourceFile?: string | null;
|
package/src/core/types/l10n.ts
CHANGED
|
@@ -414,7 +414,13 @@ export type TranslationKey =
|
|
|
414
414
|
| 'select.noResults'
|
|
415
415
|
| 'loaders.loading'
|
|
416
416
|
| 'filter.dateRange.from'
|
|
417
|
-
| 'filter.dateRange.to'
|
|
417
|
+
| 'filter.dateRange.to'
|
|
418
|
+
| 'mermaid.openFullscreen'
|
|
419
|
+
| 'mermaid.zoomIn'
|
|
420
|
+
| 'mermaid.zoomOut'
|
|
421
|
+
| 'mermaid.reset'
|
|
422
|
+
| 'mermaid.close'
|
|
423
|
+
| 'mermaid.viewer';
|
|
418
424
|
|
|
419
425
|
export type Locale = { code: string; name: string };
|
|
420
426
|
|