@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.
Files changed (32) hide show
  1. package/lib/components/SvgViewer/SvgViewer.d.ts +15 -0
  2. package/lib/components/SvgViewer/SvgViewer.js +312 -0
  3. package/lib/components/SvgViewer/variables.d.ts +1 -0
  4. package/lib/components/SvgViewer/variables.dark.d.ts +1 -0
  5. package/lib/components/SvgViewer/variables.dark.js +8 -0
  6. package/lib/components/SvgViewer/variables.js +17 -0
  7. package/lib/components/Tag/variables.dark.js +6 -0
  8. package/lib/components/Tag/variables.js +6 -0
  9. package/lib/core/styles/dark.js +2 -0
  10. package/lib/core/styles/global.js +2 -0
  11. package/lib/core/types/catalog.d.ts +1 -1
  12. package/lib/core/types/l10n.d.ts +1 -1
  13. package/lib/core/utils/transform-revisions-to-version-history.js +8 -51
  14. package/lib/icons/FitToViewIcon/FitToViewIcon.d.ts +9 -0
  15. package/lib/icons/FitToViewIcon/FitToViewIcon.js +25 -0
  16. package/lib/index.d.ts +2 -0
  17. package/lib/index.js +2 -0
  18. package/lib/markdoc/components/Mermaid/Mermaid.js +70 -2
  19. package/package.json +4 -4
  20. package/src/components/SvgViewer/SvgViewer.tsx +405 -0
  21. package/src/components/SvgViewer/variables.dark.ts +5 -0
  22. package/src/components/SvgViewer/variables.ts +14 -0
  23. package/src/components/Tag/variables.dark.ts +6 -0
  24. package/src/components/Tag/variables.ts +6 -0
  25. package/src/core/styles/dark.ts +2 -0
  26. package/src/core/styles/global.ts +2 -0
  27. package/src/core/types/catalog.ts +1 -1
  28. package/src/core/types/l10n.ts +7 -1
  29. package/src/core/utils/transform-revisions-to-version-history.ts +8 -80
  30. package/src/icons/FitToViewIcon/FitToViewIcon.tsx +26 -0
  31. package/src/index.ts +2 -0
  32. 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 = __importDefault(require("react"));
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
- return (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 }));
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.4",
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.3",
33
- "react-dom": "19.2.3",
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.2"
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,5 @@
1
+ import { css } from 'styled-components';
2
+
3
+ export const svgViewerDarkMode = css`
4
+ --svg-viewer-bg-color: var(--color-warm-grey-9);
5
+ `;
@@ -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
@@ -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: 'api' | 'file';
157
+ readonly source: 'remote' | 'file';
158
158
  relationRole: 'source' | 'target';
159
159
  relationType: EntityRelationType;
160
160
  sourceFile?: string | null;
@@ -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