@redocly/theme 0.67.0-next.1 → 0.67.0-next.3

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.
@@ -3,11 +3,14 @@ import type { JSX } from 'react';
3
3
  export type ImageProps = {
4
4
  src?: string;
5
5
  srcSet?: string;
6
+ images?: string[];
6
7
  alt?: string;
7
8
  className?: string;
8
9
  width?: string | number;
9
10
  height?: string | number;
10
11
  border?: string;
12
+ caption?: string;
13
+ framed?: boolean;
11
14
  withLightbox?: boolean;
12
15
  lightboxStyle?: React.CSSProperties | string;
13
16
  style?: React.CSSProperties | string;
@@ -32,17 +32,16 @@ var __importStar = (this && this.__importStar) || (function () {
32
32
  return result;
33
33
  };
34
34
  })();
35
- var __importDefault = (this && this.__importDefault) || function (mod) {
36
- return (mod && mod.__esModule) ? mod : { "default": mod };
37
- };
38
35
  Object.defineProperty(exports, "__esModule", { value: true });
39
36
  exports.Image = Image;
40
37
  const react_1 = __importStar(require("react"));
41
- const styled_components_1 = __importDefault(require("styled-components"));
38
+ const styled_components_1 = __importStar(require("styled-components"));
42
39
  const utils_1 = require("../../core/utils");
43
40
  const hooks_1 = require("../../core/hooks");
41
+ const CloseIcon_1 = require("../../icons/CloseIcon/CloseIcon");
42
+ const Button_1 = require("../../components/Button/Button");
44
43
  function Image(props) {
45
- const { src, srcSet, alt, className, width, height, border, style, withLightbox, lightboxStyle } = props;
44
+ const { src, srcSet, images: galleryImages, alt, className, width, height, border, caption, framed, style, withLightbox, lightboxStyle, } = props;
46
45
  const lightboxContainerRef = (0, react_1.useRef)(null);
47
46
  const [lightboxImage, setLightboxImage] = (0, react_1.useState)(undefined);
48
47
  const parsedSourceSetMap = (0, react_1.useMemo)(() => {
@@ -72,12 +71,73 @@ function Image(props) {
72
71
  }, [lightboxImage]);
73
72
  const combinedStyles = Object.assign(Object.assign(Object.assign({}, (withLightbox && { cursor: 'pointer' })), (border && { border })), (typeof style === 'string' ? (0, utils_1.parseStyleString)(style) : style));
74
73
  const lightboxOverlayStyles = typeof lightboxStyle === 'string' ? (0, utils_1.parseStyleString)(lightboxStyle) : lightboxStyle;
74
+ const images = src ? (react_1.default.createElement("img", { src: src, alt: alt, className: className, width: width, height: height, style: combinedStyles, onClick: () => handleImageClick(src) })) : (Array.from(parsedSourceSetMap).map(([key, value]) => (react_1.default.createElement(ColorModeAwareImage, { key: key, $colorMode: key, src: value, alt: alt, className: className, width: width, height: height, $withLightbox: withLightbox, style: combinedStyles, onClick: () => handleImageClick(value) }))));
75
+ const hasGallery = Array.isArray(galleryImages) && galleryImages.length > 0;
76
+ const visibleGalleryImages = hasGallery ? galleryImages.slice(0, 3) : [];
77
+ const gallery = hasGallery ? (react_1.default.createElement(GalleryRow, null, visibleGalleryImages.map((galleryImageSrc, index) => (react_1.default.createElement("img", { key: `${galleryImageSrc}-${index}`, src: galleryImageSrc, alt: alt, style: withLightbox ? { cursor: 'pointer' } : undefined, onClick: () => handleImageClick(galleryImageSrc) }))))) : null;
78
+ const hasCaption = Boolean(caption);
79
+ const hasWrapper = Boolean(framed || hasCaption || hasGallery);
80
+ const wrapperContent = hasGallery ? gallery : images;
75
81
  return (react_1.default.createElement(react_1.default.Fragment, null,
76
82
  lightboxImage ? (react_1.default.createElement(LightboxContainer, { ref: lightboxContainerRef, onClick: handleCloseLightbox, onKeyDown: handleLightboxKeyDown, tabIndex: 0 },
77
83
  react_1.default.createElement(Overlay, { style: lightboxOverlayStyles }),
78
- react_1.default.createElement(Image, { src: lightboxImage, alt: alt }))) : null,
79
- src ? (react_1.default.createElement("img", { src: src, alt: alt, className: className, width: width, height: height, style: combinedStyles, onClick: () => handleImageClick(src) })) : (Array.from(parsedSourceSetMap).map(([key, value]) => (react_1.default.createElement(ColorModeAwareImage, { key: key, $colorMode: key, src: value, alt: alt, className: className, width: width, height: height, $withLightbox: withLightbox, style: combinedStyles, onClick: () => handleImageClick(value) }))))));
84
+ react_1.default.createElement(LightboxContent, null,
85
+ react_1.default.createElement(CloseButton, { variant: "secondary", "aria-label": "Close image", onClick: handleCloseLightbox },
86
+ react_1.default.createElement(CloseIcon_1.CloseIcon, null)),
87
+ react_1.default.createElement(Image, { src: lightboxImage, alt: alt })))) : null,
88
+ hasWrapper ? (react_1.default.createElement(ImageFrame, { "data-component-name": "Image/Image", $framed: framed, $gallery: hasGallery },
89
+ wrapperContent,
90
+ hasCaption ? react_1.default.createElement(ImageCaption, null, caption) : null)) : (images)));
80
91
  }
92
+ const ImageFrame = styled_components_1.default.figure `
93
+ display: ${({ $gallery }) => ($gallery ? 'block' : 'inline-block')};
94
+ margin: 0;
95
+ ${({ $gallery }) => ($gallery ? 'width: 100%;' : '')}
96
+ max-width: 100%;
97
+
98
+ ${({ $framed }) => $framed &&
99
+ (0, styled_components_1.css) `
100
+ padding: var(--image-frame-padding);
101
+ border: var(--border-width) var(--border-style) var(--image-frame-border-color);
102
+ border-radius: var(--image-frame-border-radius);
103
+ background-color: var(--image-frame-bg-color);
104
+ `}
105
+
106
+ > img {
107
+ display: block;
108
+ max-width: 100%;
109
+ height: auto;
110
+ background: transparent center / cover no-repeat;
111
+
112
+ ${({ $framed }) => $framed &&
113
+ (0, styled_components_1.css) `
114
+ border-radius: var(--image-frame-image-border-radius);
115
+ `}
116
+ }
117
+ `;
118
+ const GalleryRow = styled_components_1.default.div `
119
+ display: flex;
120
+ align-items: stretch;
121
+ overflow: hidden;
122
+ border-radius: var(--image-gallery-border-radius);
123
+
124
+ img {
125
+ flex: 1 1 0;
126
+ min-width: 0;
127
+ width: 100%;
128
+ object-fit: cover;
129
+ border-radius: 0;
130
+ background: var(--image-gallery-image-bg-color) 50% / cover no-repeat;
131
+ }
132
+ `;
133
+ const ImageCaption = styled_components_1.default.figcaption `
134
+ padding: var(--image-caption-padding);
135
+ color: var(--image-caption-text-color);
136
+ font-size: var(--image-caption-font-size);
137
+ line-height: var(--image-caption-line-height);
138
+ font-weight: var(--image-caption-font-weight);
139
+ text-align: center;
140
+ `;
81
141
  const ColorModeAwareImage = styled_components_1.default.img `
82
142
  html:not(.${(props) => props.$colorMode}) && {
83
143
  display: none;
@@ -88,13 +148,38 @@ const ColorModeAwareImage = styled_components_1.default.img `
88
148
  `}
89
149
  `;
90
150
  const Overlay = styled_components_1.default.div `
91
- background-color: var(--bg-color-modal-overlay);
151
+ background-color: var(--image-lightbox-overlay-bg-color);
92
152
  grid-column: 1 / 2;
93
153
  grid-row: 1 / 2;
94
154
  height: 100%;
95
155
  width: 100%;
96
156
  z-index: -1;
97
157
  `;
158
+ const CloseButton = (0, styled_components_1.default)(Button_1.Button) `
159
+ position: absolute;
160
+ top: 0;
161
+ left: 100%;
162
+ margin-left: var(--image-lightbox-close-offset);
163
+ width: var(--image-lightbox-close-size);
164
+ height: var(--image-lightbox-close-size);
165
+ border-radius: var(--image-lightbox-close-border-radius);
166
+ z-index: 1;
167
+
168
+ svg {
169
+ width: var(--image-lightbox-close-icon-size);
170
+ height: var(--image-lightbox-close-icon-size);
171
+ }
172
+ `;
173
+ const LightboxContent = styled_components_1.default.div `
174
+ position: relative;
175
+ grid-column: 1 / 2;
176
+ grid-row: 1 / 2;
177
+ margin: auto;
178
+ width: fit-content;
179
+ height: fit-content;
180
+ max-width: var(--image-lightbox-content-max-width);
181
+ max-height: var(--image-lightbox-content-max-height);
182
+ `;
98
183
  const LightboxContainer = styled_components_1.default.div `
99
184
  display: grid;
100
185
  height: 100vh;
@@ -110,11 +195,14 @@ const LightboxContainer = styled_components_1.default.div `
110
195
 
111
196
  img {
112
197
  cursor: pointer;
113
- grid-column: 1 / 2;
114
- grid-row: 1 / 2;
115
- margin: auto;
116
- max-width: 90%;
117
- max-height: 90%;
198
+ display: block;
199
+ max-width: var(--image-lightbox-image-max-width);
200
+ max-height: var(--image-lightbox-image-max-height);
201
+ border-radius: var(--image-lightbox-image-border-radius);
202
+ background:
203
+ var(--image-lightbox-image-bg-placeholder) center / cover no-repeat,
204
+ var(--image-frame-bg-color);
205
+ box-shadow: var(--image-lightbox-image-shadow);
118
206
  }
119
207
  `;
120
208
  //# sourceMappingURL=Image.js.map
@@ -0,0 +1 @@
1
+ export declare const image: import("styled-components").RuleSet<object>;
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.image = void 0;
4
+ const styled_components_1 = require("styled-components");
5
+ exports.image = (0, styled_components_1.css) `
6
+ /* === Image === */
7
+
8
+ /**
9
+ * @tokens Image frame
10
+ */
11
+ --image-frame-padding: var(--spacing-xs);
12
+ --image-frame-border-color: var(--border-color-secondary);
13
+ --image-frame-border-radius: var(--border-radius-xxl);
14
+ --image-frame-bg-color: var(--bg-color-raised);
15
+ --image-frame-image-border-radius: var(--border-radius-lg);
16
+
17
+ /**
18
+ * @tokens Image caption
19
+ */
20
+ --image-caption-padding: var(--spacing-sm) var(--spacing-lg) var(--spacing-xxs) var(--spacing-lg);
21
+ --image-caption-text-color: var(--text-color-primary);
22
+ --image-caption-font-size: var(--font-size-base);
23
+ --image-caption-line-height: var(--line-height-base);
24
+ --image-caption-font-weight: var(--font-weight-regular);
25
+
26
+ /**
27
+ * @tokens Image gallery
28
+ */
29
+ --image-gallery-border-radius: var(--border-radius-lg);
30
+ --image-gallery-image-bg-color: var(--bg-color-tonal);
31
+
32
+ /**
33
+ * @tokens Image lightbox
34
+ */
35
+ --image-lightbox-overlay-bg-color: var(--bg-color-modal-overlay);
36
+ --image-lightbox-content-max-width: 90%;
37
+ --image-lightbox-content-max-height: 90%;
38
+
39
+ --image-lightbox-image-max-width: min(
40
+ 90vw,
41
+ calc(100vw - 2 * var(--image-lightbox-side-gutter))
42
+ );
43
+ --image-lightbox-image-max-height: 90vh;
44
+ --image-lightbox-image-border-radius: var(--border-radius-xl);
45
+ --image-lightbox-image-bg-placeholder: var(--bg-color-tonal);
46
+ --image-lightbox-image-shadow: var(--bg-raised-shadow);
47
+
48
+ /**
49
+ * @tokens Image lightbox close button
50
+ */
51
+ --image-lightbox-close-size: 40px;
52
+ --image-lightbox-close-icon-size: 18px;
53
+ --image-lightbox-close-offset: var(--spacing-base);
54
+ --image-lightbox-close-border-radius: var(--border-radius-lg);
55
+ --image-lightbox-side-gutter: calc(
56
+ var(--image-lightbox-close-size) + var(--image-lightbox-close-offset) + var(--spacing-sm)
57
+ );
58
+ `;
59
+ //# sourceMappingURL=variables.js.map
@@ -10,43 +10,44 @@ const variables_5 = require("../../components/Breadcrumbs/variables");
10
10
  const variables_6 = require("../../components/Tag/variables");
11
11
  const variables_7 = require("../../components/TableOfContent/variables");
12
12
  const variables_8 = require("../../components/Catalog/variables");
13
- const variables_9 = require("../../components/Filter/variables");
14
- const variables_10 = require("../../components/CatalogClassic/variables");
15
- const variables_11 = require("../../components/Panel/variables");
16
- const variables_12 = require("../../components/Accordion/variables");
17
- const variables_13 = require("../../components/Select/variables");
18
- const variables_14 = require("../../components/Dropdown/variables");
19
- const variables_15 = require("../../components/Tooltip/variables");
20
- const variables_16 = require("../../icons/CheckboxIcon/variables");
21
- const variables_17 = require("../../components/Admonition/variables");
22
- const variables_18 = require("../../components/Footer/variables");
23
- const variables_19 = require("../../components/Button/variables");
24
- const variables_20 = require("../../components/Buttons/variables");
25
- const variables_21 = require("../../components/Navbar/variables");
26
- const variables_22 = require("../../components/Search/variables");
27
- const variables_23 = require("../../components/Menu/variables");
28
- const variables_24 = require("../../components/CodeBlock/variables");
29
- const variables_25 = require("../../components/Product/variables");
30
- const variables_26 = require("../../components/Markdown/variables");
31
- const variables_27 = require("../../components/Banner/variables");
32
- const variables_28 = require("../../markdoc/components/Tabs/variables");
33
- const variables_29 = require("../../markdoc/components/Diagram/variables");
34
- const variables_30 = require("../../components/LastUpdated/variables");
35
- const variables_31 = require("../../components/Logo/variables");
36
- const variables_32 = require("../../components/StatusCode/variables");
37
- const variables_33 = require("../../components/Segmented/variables");
38
- const variables_34 = require("../../components/UserMenu/variables");
39
- const variables_35 = require("../../components/Tags/variables");
40
- const variables_36 = require("../../components/VersionPicker/variables");
41
- const variables_37 = require("../../components/DatePicker/variables");
42
- const variables_38 = require("../../components/Switch/variables");
43
- const variables_39 = require("../../markdoc/components/Cards/variables");
44
- const variables_40 = require("../../markdoc/components/CodeWalkthrough/variables");
45
- const variables_41 = require("../../components/SkipContent/variables");
46
- const variables_42 = require("../../components/PageActions/variables");
47
- const variables_43 = require("../../components/SvgViewer/variables");
48
- const variables_44 = require("../../components/Toast/variables");
49
- const variables_45 = require("../../components/PageNavigation/variables");
13
+ const variables_9 = require("../../components/Image/variables");
14
+ const variables_10 = require("../../components/Filter/variables");
15
+ const variables_11 = require("../../components/CatalogClassic/variables");
16
+ const variables_12 = require("../../components/Panel/variables");
17
+ const variables_13 = require("../../components/Accordion/variables");
18
+ const variables_14 = require("../../components/Select/variables");
19
+ const variables_15 = require("../../components/Dropdown/variables");
20
+ const variables_16 = require("../../components/Tooltip/variables");
21
+ const variables_17 = require("../../icons/CheckboxIcon/variables");
22
+ const variables_18 = require("../../components/Admonition/variables");
23
+ const variables_19 = require("../../components/Footer/variables");
24
+ const variables_20 = require("../../components/Button/variables");
25
+ const variables_21 = require("../../components/Buttons/variables");
26
+ const variables_22 = require("../../components/Navbar/variables");
27
+ const variables_23 = require("../../components/Search/variables");
28
+ const variables_24 = require("../../components/Menu/variables");
29
+ const variables_25 = require("../../components/CodeBlock/variables");
30
+ const variables_26 = require("../../components/Product/variables");
31
+ const variables_27 = require("../../components/Markdown/variables");
32
+ const variables_28 = require("../../components/Banner/variables");
33
+ const variables_29 = require("../../markdoc/components/Tabs/variables");
34
+ const variables_30 = require("../../markdoc/components/Diagram/variables");
35
+ const variables_31 = require("../../components/LastUpdated/variables");
36
+ const variables_32 = require("../../components/Logo/variables");
37
+ const variables_33 = require("../../components/StatusCode/variables");
38
+ const variables_34 = require("../../components/Segmented/variables");
39
+ const variables_35 = require("../../components/UserMenu/variables");
40
+ const variables_36 = require("../../components/Tags/variables");
41
+ const variables_37 = require("../../components/VersionPicker/variables");
42
+ const variables_38 = require("../../components/DatePicker/variables");
43
+ const variables_39 = require("../../components/Switch/variables");
44
+ const variables_40 = require("../../markdoc/components/Cards/variables");
45
+ const variables_41 = require("../../markdoc/components/CodeWalkthrough/variables");
46
+ const variables_42 = require("../../components/SkipContent/variables");
47
+ const variables_43 = require("../../components/PageActions/variables");
48
+ const variables_44 = require("../../components/SvgViewer/variables");
49
+ const variables_45 = require("../../components/Toast/variables");
50
+ const variables_46 = require("../../components/PageNavigation/variables");
50
51
  const palette_1 = require("./palette");
51
52
  const dark_1 = require("./dark");
52
53
  const themeColors = (0, styled_components_1.css) `
@@ -1255,47 +1256,48 @@ const replay = (0, styled_components_1.css) `
1255
1256
  `;
1256
1257
  exports.styles = (0, styled_components_1.css) `
1257
1258
  :root {
1258
- ${variables_12.accordion}
1259
- ${variables_17.admonition}
1259
+ ${variables_13.accordion}
1260
+ ${variables_18.admonition}
1260
1261
  ${apiReferenceDocs}
1261
- ${variables_11.apiReferencePanels}
1262
+ ${variables_12.apiReferencePanels}
1262
1263
  ${badges}
1263
- ${variables_27.banner}
1264
+ ${variables_28.banner}
1264
1265
  ${borders}
1265
1266
  ${variables_5.breadcrumbs}
1266
- ${variables_19.button}
1267
- ${variables_20.aiAssistantButton}
1268
- ${variables_20.connectMCPButton}
1269
- ${variables_39.cards}
1267
+ ${variables_20.button}
1268
+ ${variables_21.aiAssistantButton}
1269
+ ${variables_21.connectMCPButton}
1270
+ ${variables_40.cards}
1270
1271
  ${variables_8.catalog}
1271
- ${variables_10.catalogClassic}
1272
- ${variables_24.code}
1273
- ${variables_40.codeWalkthrough}
1272
+ ${variables_11.catalogClassic}
1273
+ ${variables_25.code}
1274
+ ${variables_41.codeWalkthrough}
1274
1275
  ${docsDropdown}
1275
- ${variables_14.dropdown}
1276
+ ${variables_15.dropdown}
1276
1277
  ${error}
1277
- ${variables_9.filter}
1278
- ${variables_18.footer}
1278
+ ${variables_10.filter}
1279
+ ${variables_19.footer}
1279
1280
  ${headingsTypography}
1280
- ${variables_35.httpTag}
1281
+ ${variables_36.httpTag}
1282
+ ${variables_9.image}
1281
1283
  ${inputs}
1282
1284
  ${variables_1.languagePicker}
1283
- ${variables_30.lastUpdated}
1285
+ ${variables_31.lastUpdated}
1284
1286
  ${links}
1285
1287
  ${loadProgressBar}
1286
- ${variables_31.logo}
1287
- ${variables_26.markdown}
1288
- ${variables_28.markdownTabs}
1289
- ${variables_29.diagram}
1290
- ${variables_23.menu}
1291
- ${variables_23.mobileMenu}
1288
+ ${variables_32.logo}
1289
+ ${variables_27.markdown}
1290
+ ${variables_29.markdownTabs}
1291
+ ${variables_30.diagram}
1292
+ ${variables_24.menu}
1293
+ ${variables_24.mobileMenu}
1292
1294
  ${modal}
1293
- ${variables_21.navbar}
1295
+ ${variables_22.navbar}
1294
1296
  ${pages}
1295
- ${variables_25.productPicker}
1296
- ${variables_11.responsePanelColors}
1297
- ${variables_22.search}
1298
- ${variables_13.select}
1297
+ ${variables_26.productPicker}
1298
+ ${variables_12.responsePanelColors}
1299
+ ${variables_23.search}
1300
+ ${variables_14.select}
1299
1301
  ${variables_4.sidebar}
1300
1302
  ${sizeAndSpace}
1301
1303
  ${variables_6.tag}
@@ -1303,28 +1305,28 @@ exports.styles = (0, styled_components_1.css) `
1303
1305
  ${palette_1.activeBrandPaletteLight}
1304
1306
  ${tile}
1305
1307
  ${variables_7.toc}
1306
- ${variables_15.tooltip}
1308
+ ${variables_16.tooltip}
1307
1309
  ${typography}
1308
- ${variables_34.userMenu}
1309
- ${variables_36.versionPicker}
1310
+ ${variables_35.userMenu}
1311
+ ${variables_37.versionPicker}
1310
1312
  ${zIndexDepth}
1311
1313
  ${scorecardColors}
1312
- ${variables_32.statusCode}
1314
+ ${variables_33.statusCode}
1313
1315
  ${tab}
1314
1316
  ${icon}
1315
1317
  ${tree}
1316
- ${variables_33.segmented}
1317
- ${variables_38.switcher}
1318
- ${variables_16.checkbox}
1318
+ ${variables_34.segmented}
1319
+ ${variables_39.switcher}
1320
+ ${variables_17.checkbox}
1319
1321
  ${variables_3.feedback}
1320
1322
  ${variables_2.scorecard}
1321
- ${variables_37.datePicker}
1323
+ ${variables_38.datePicker}
1322
1324
  ${replay}
1323
- ${variables_41.skipContent}
1324
- ${variables_42.pageActions}
1325
- ${variables_43.svgViewer}
1326
- ${variables_44.toast}
1327
- ${variables_45.pageNavigation}
1325
+ ${variables_42.skipContent}
1326
+ ${variables_43.pageActions}
1327
+ ${variables_44.svgViewer}
1328
+ ${variables_45.toast}
1329
+ ${variables_46.pageNavigation}
1328
1330
 
1329
1331
  background-color: var(--bg-color);
1330
1332
  color: var(--text-color-primary);
@@ -0,0 +1,35 @@
1
+ export type SecurityDetails = {
2
+ password?: string;
3
+ username?: string;
4
+ token?: {
5
+ token_type?: string;
6
+ access_token: string;
7
+ };
8
+ client_id?: string;
9
+ client_secret?: string;
10
+ scopes?: string[];
11
+ };
12
+ export type ConfigureInputHintAction = {
13
+ label: string;
14
+ action: () => void;
15
+ };
16
+ export type ConfigureInputHint = {
17
+ title: string;
18
+ text: string;
19
+ actions?: ConfigureInputHintAction[];
20
+ };
21
+ export type ConfigureRequestValues = {
22
+ headers?: Record<string, string>;
23
+ body?: Record<string, any>;
24
+ query?: Record<string, string>;
25
+ path?: Record<string, string>;
26
+ cookie?: Record<string, string>;
27
+ security?: Record<string, SecurityDetails>;
28
+ envVariables?: Record<string, string>;
29
+ serverVariables?: Record<string, string>;
30
+ inputHints?: Record<string, ConfigureInputHint>;
31
+ };
32
+ export type ConfigureServerRequestValues = {
33
+ [serverUrl: string]: ConfigureRequestValues;
34
+ };
35
+ export type ConfigureReplayConfig = ConfigureRequestValues | ConfigureServerRequestValues | null | undefined;
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=configure-helpers.js.map
@@ -1,28 +1,7 @@
1
1
  import type { UserClaims, OpenAPIServer } from '../core/types';
2
- export type SecurityDetails = {
3
- password?: string;
4
- username?: string;
5
- token?: {
6
- token_type?: string;
7
- access_token: string;
8
- };
9
- client_id?: string;
10
- client_secret?: string;
11
- scopes?: string[];
12
- };
13
- export type ConfigureRequestValues = {
14
- headers?: Record<string, string>;
15
- body?: Record<string, any>;
16
- query?: Record<string, string>;
17
- path?: Record<string, string>;
18
- cookie?: Record<string, string>;
19
- security?: Record<string, SecurityDetails>;
20
- envVariables?: Record<string, string>;
21
- serverVariables?: Record<string, string>;
22
- };
23
- export type ConfigureServerRequestValues = {
24
- [serverUrl: string]: ConfigureRequestValues;
25
- };
2
+ export * from '../ext/configure-helpers';
3
+ export * from '../ext/helpers/is-direct-configure-request-values';
4
+ import type { ConfigureRequestValues, ConfigureServerRequestValues } from '../ext/configure-helpers';
26
5
  type Configure = {
27
6
  requestValues?: ConfigureRequestValues | ConfigureServerRequestValues;
28
7
  };
@@ -38,4 +17,3 @@ type ContextProps = {
38
17
  servers: OpenAPIServer[];
39
18
  };
40
19
  export declare function configure(_context: ContextProps): Configure;
41
- export {};
@@ -1,6 +1,22 @@
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 __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
2
16
  Object.defineProperty(exports, "__esModule", { value: true });
3
17
  exports.configure = configure;
18
+ __exportStar(require("../ext/configure-helpers"), exports);
19
+ __exportStar(require("../ext/helpers/is-direct-configure-request-values"), exports);
4
20
  function configure(_context) {
5
21
  // const exampleDefaultRequestValues: ConfigureRequestValues = {
6
22
  // body: {
@@ -0,0 +1,15 @@
1
+ import type { ConfigureRequestValues, ConfigureServerRequestValues } from '../../ext/configure-helpers';
2
+ /**
3
+ * Type guard that distinguishes a direct {@link ConfigureRequestValues} payload (request
4
+ * values that apply to every server) from a {@link ConfigureServerRequestValues} payload
5
+ * (request values keyed by server URL).
6
+ *
7
+ * A payload is treated as server-keyed when at least one top-level key is *not* a known
8
+ * request-value key (e.g. `headers`, `security`, `inputHints`) and its value looks like a
9
+ * nested {@link ConfigureRequestValues}. This keeps mixed shapes — e.g. server-keyed values
10
+ * alongside a top-level `inputHints` sibling — classified as server-keyed.
11
+ *
12
+ * @param value - The configure payload returned from `getReplayConfiguration`.
13
+ * @returns `true` when `value` is a direct {@link ConfigureRequestValues}, narrowing the type.
14
+ */
15
+ export declare function isDirectConfigureRequestValues(value: ConfigureRequestValues | ConfigureServerRequestValues): value is ConfigureRequestValues;
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isDirectConfigureRequestValues = isDirectConfigureRequestValues;
4
+ const CONFIGURE_REQUEST_VALUE_KEYS = [
5
+ 'headers',
6
+ 'body',
7
+ 'query',
8
+ 'path',
9
+ 'cookie',
10
+ 'security',
11
+ 'envVariables',
12
+ 'serverVariables',
13
+ 'inputHints',
14
+ ];
15
+ const CONFIGURE_REQUEST_VALUE_KEY_SET = new Set(CONFIGURE_REQUEST_VALUE_KEYS);
16
+ function hasConfigureRequestValueKeys(value) {
17
+ return CONFIGURE_REQUEST_VALUE_KEYS.some((key) => key in value);
18
+ }
19
+ /**
20
+ * Type guard that distinguishes a direct {@link ConfigureRequestValues} payload (request
21
+ * values that apply to every server) from a {@link ConfigureServerRequestValues} payload
22
+ * (request values keyed by server URL).
23
+ *
24
+ * A payload is treated as server-keyed when at least one top-level key is *not* a known
25
+ * request-value key (e.g. `headers`, `security`, `inputHints`) and its value looks like a
26
+ * nested {@link ConfigureRequestValues}. This keeps mixed shapes — e.g. server-keyed values
27
+ * alongside a top-level `inputHints` sibling — classified as server-keyed.
28
+ *
29
+ * @param value - The configure payload returned from `getReplayConfiguration`.
30
+ * @returns `true` when `value` is a direct {@link ConfigureRequestValues}, narrowing the type.
31
+ */
32
+ function isDirectConfigureRequestValues(value) {
33
+ const hasServerKeyedValues = Object.entries(value).some(([key, entry]) => !CONFIGURE_REQUEST_VALUE_KEY_SET.has(key) &&
34
+ entry != null &&
35
+ typeof entry === 'object' &&
36
+ hasConfigureRequestValueKeys(entry));
37
+ if (hasServerKeyedValues) {
38
+ return false;
39
+ }
40
+ return hasConfigureRequestValueKeys(value);
41
+ }
42
+ //# sourceMappingURL=is-direct-configure-request-values.js.map
@@ -1,4 +1,4 @@
1
- import type { ConfigureRequestValues, ConfigureServerRequestValues } from '../ext/configure';
1
+ import { type ConfigureRequestValues, type ConfigureServerRequestValues } from '../ext/configure';
2
2
  import type { UserClaims, OpenAPIServer, OpenAPIInfo } from '../core/types';
3
3
  type ContextProps = {
4
4
  operation: {
@@ -14,9 +14,20 @@ exports.img = {
14
14
  type: String,
15
15
  resolver: 'imageSrcSet',
16
16
  },
17
+ images: {
18
+ type: Array,
19
+ resolver: 'imageGallery',
20
+ },
17
21
  alt: {
18
22
  type: String,
19
23
  },
24
+ caption: {
25
+ type: String,
26
+ },
27
+ framed: {
28
+ type: Boolean,
29
+ default: false,
30
+ },
20
31
  withLightbox: {
21
32
  type: Boolean,
22
33
  default: false,
@@ -45,8 +56,31 @@ exports.img = {
45
56
  },
46
57
  },
47
58
  validate: (node) => {
48
- if (!node.attributes.src && !node.attributes.srcSet) {
49
- return [{ id: '', message: 'src or srcSet is required', level: 'error' }];
59
+ const { src, srcSet, images } = node.attributes;
60
+ const hasGallery = Array.isArray(images) && images.length > 0;
61
+ if (!src && !srcSet && !hasGallery) {
62
+ return [{ id: '', message: 'src, srcSet, or images is required', level: 'error' }];
63
+ }
64
+ if (hasGallery && (src || srcSet)) {
65
+ return [
66
+ {
67
+ id: 'invalid-img-source-combination',
68
+ message: 'images cannot be used together with src or srcSet',
69
+ level: 'error',
70
+ },
71
+ ];
72
+ }
73
+ if (src && srcSet) {
74
+ return [
75
+ {
76
+ id: 'invalid-img-source-combination',
77
+ message: 'src cannot be used together with srcSet',
78
+ level: 'error',
79
+ },
80
+ ];
81
+ }
82
+ if (Array.isArray(images) && images.length > 3) {
83
+ return [{ id: '', message: 'images supports a maximum of 3 items', level: 'error' }];
50
84
  }
51
85
  return [];
52
86
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@redocly/theme",
3
- "version": "0.67.0-next.1",
3
+ "version": "0.67.0-next.3",
4
4
  "description": "Shared UI components lib",
5
5
  "keywords": [
6
6
  "theme",
@@ -63,7 +63,7 @@
63
63
  "vitest": "4.1.8",
64
64
  "vitest-when": "0.6.2",
65
65
  "webpack": "5.105.2",
66
- "@redocly/realm-asyncapi-sdk": "0.13.0-next.0"
66
+ "@redocly/realm-asyncapi-sdk": "0.13.0-next.2"
67
67
  },
68
68
  "dependencies": {
69
69
  "@tanstack/react-query": "5.62.3",
@@ -1,27 +1,45 @@
1
1
  import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
2
- import styled from 'styled-components';
2
+ import styled, { css } from 'styled-components';
3
3
 
4
4
  import type { KeyboardEvent, JSX } from 'react';
5
5
 
6
6
  import { parseSrcSet, parseStyleString } from '@redocly/theme/core/utils';
7
7
  import { useModalScrollLock } from '@redocly/theme/core/hooks';
8
+ import { CloseIcon } from '@redocly/theme/icons/CloseIcon/CloseIcon';
9
+ import { Button } from '@redocly/theme/components/Button/Button';
8
10
 
9
11
  export type ImageProps = {
10
12
  src?: string;
11
13
  srcSet?: string;
14
+ images?: string[];
12
15
  alt?: string;
13
16
  className?: string;
14
17
  width?: string | number;
15
18
  height?: string | number;
16
19
  border?: string;
20
+ caption?: string;
21
+ framed?: boolean;
17
22
  withLightbox?: boolean;
18
23
  lightboxStyle?: React.CSSProperties | string;
19
24
  style?: React.CSSProperties | string;
20
25
  };
21
26
 
22
27
  export function Image(props: ImageProps): JSX.Element {
23
- const { src, srcSet, alt, className, width, height, border, style, withLightbox, lightboxStyle } =
24
- props;
28
+ const {
29
+ src,
30
+ srcSet,
31
+ images: galleryImages,
32
+ alt,
33
+ className,
34
+ width,
35
+ height,
36
+ border,
37
+ caption,
38
+ framed,
39
+ style,
40
+ withLightbox,
41
+ lightboxStyle,
42
+ } = props;
25
43
 
26
44
  const lightboxContainerRef = useRef<HTMLDivElement>(null);
27
45
  const [lightboxImage, setLightboxImage] = useState<string | undefined>(undefined);
@@ -66,6 +84,54 @@ export function Image(props: ImageProps): JSX.Element {
66
84
  const lightboxOverlayStyles: React.CSSProperties | undefined =
67
85
  typeof lightboxStyle === 'string' ? parseStyleString(lightboxStyle) : lightboxStyle;
68
86
 
87
+ const images = src ? (
88
+ <img
89
+ src={src}
90
+ alt={alt}
91
+ className={className}
92
+ width={width}
93
+ height={height}
94
+ style={combinedStyles}
95
+ onClick={() => handleImageClick(src)}
96
+ />
97
+ ) : (
98
+ Array.from(parsedSourceSetMap).map(([key, value]) => (
99
+ <ColorModeAwareImage
100
+ key={key}
101
+ $colorMode={key}
102
+ src={value}
103
+ alt={alt}
104
+ className={className}
105
+ width={width}
106
+ height={height}
107
+ $withLightbox={withLightbox}
108
+ style={combinedStyles}
109
+ onClick={() => handleImageClick(value)}
110
+ />
111
+ ))
112
+ );
113
+
114
+ const hasGallery = Array.isArray(galleryImages) && galleryImages.length > 0;
115
+ const visibleGalleryImages = hasGallery ? galleryImages.slice(0, 3) : [];
116
+
117
+ const gallery = hasGallery ? (
118
+ <GalleryRow>
119
+ {visibleGalleryImages.map((galleryImageSrc, index) => (
120
+ <img
121
+ key={`${galleryImageSrc}-${index}`}
122
+ src={galleryImageSrc}
123
+ alt={alt}
124
+ style={withLightbox ? { cursor: 'pointer' } : undefined}
125
+ onClick={() => handleImageClick(galleryImageSrc)}
126
+ />
127
+ ))}
128
+ </GalleryRow>
129
+ ) : null;
130
+
131
+ const hasCaption = Boolean(caption);
132
+ const hasWrapper = Boolean(framed || hasCaption || hasGallery);
133
+ const wrapperContent = hasGallery ? gallery : images;
134
+
69
135
  return (
70
136
  <>
71
137
  {lightboxImage ? (
@@ -76,39 +142,80 @@ export function Image(props: ImageProps): JSX.Element {
76
142
  tabIndex={0}
77
143
  >
78
144
  <Overlay style={lightboxOverlayStyles} />
79
- <Image src={lightboxImage} alt={alt} />
145
+ <LightboxContent>
146
+ <CloseButton variant="secondary" aria-label="Close image" onClick={handleCloseLightbox}>
147
+ <CloseIcon />
148
+ </CloseButton>
149
+ <Image src={lightboxImage} alt={alt} />
150
+ </LightboxContent>
80
151
  </LightboxContainer>
81
152
  ) : null}
82
- {src ? (
83
- <img
84
- src={src}
85
- alt={alt}
86
- className={className}
87
- width={width}
88
- height={height}
89
- style={combinedStyles}
90
- onClick={() => handleImageClick(src)}
91
- />
153
+ {hasWrapper ? (
154
+ <ImageFrame data-component-name="Image/Image" $framed={framed} $gallery={hasGallery}>
155
+ {wrapperContent}
156
+ {hasCaption ? <ImageCaption>{caption}</ImageCaption> : null}
157
+ </ImageFrame>
92
158
  ) : (
93
- Array.from(parsedSourceSetMap).map(([key, value]) => (
94
- <ColorModeAwareImage
95
- key={key}
96
- $colorMode={key}
97
- src={value}
98
- alt={alt}
99
- className={className}
100
- width={width}
101
- height={height}
102
- $withLightbox={withLightbox}
103
- style={combinedStyles}
104
- onClick={() => handleImageClick(value)}
105
- />
106
- ))
159
+ images
107
160
  )}
108
161
  </>
109
162
  );
110
163
  }
111
164
 
165
+ const ImageFrame = styled.figure<{ $framed?: boolean; $gallery?: boolean }>`
166
+ display: ${({ $gallery }) => ($gallery ? 'block' : 'inline-block')};
167
+ margin: 0;
168
+ ${({ $gallery }) => ($gallery ? 'width: 100%;' : '')}
169
+ max-width: 100%;
170
+
171
+ ${({ $framed }) =>
172
+ $framed &&
173
+ css`
174
+ padding: var(--image-frame-padding);
175
+ border: var(--border-width) var(--border-style) var(--image-frame-border-color);
176
+ border-radius: var(--image-frame-border-radius);
177
+ background-color: var(--image-frame-bg-color);
178
+ `}
179
+
180
+ > img {
181
+ display: block;
182
+ max-width: 100%;
183
+ height: auto;
184
+ background: transparent center / cover no-repeat;
185
+
186
+ ${({ $framed }) =>
187
+ $framed &&
188
+ css`
189
+ border-radius: var(--image-frame-image-border-radius);
190
+ `}
191
+ }
192
+ `;
193
+
194
+ const GalleryRow = styled.div`
195
+ display: flex;
196
+ align-items: stretch;
197
+ overflow: hidden;
198
+ border-radius: var(--image-gallery-border-radius);
199
+
200
+ img {
201
+ flex: 1 1 0;
202
+ min-width: 0;
203
+ width: 100%;
204
+ object-fit: cover;
205
+ border-radius: 0;
206
+ background: var(--image-gallery-image-bg-color) 50% / cover no-repeat;
207
+ }
208
+ `;
209
+
210
+ const ImageCaption = styled.figcaption`
211
+ padding: var(--image-caption-padding);
212
+ color: var(--image-caption-text-color);
213
+ font-size: var(--image-caption-font-size);
214
+ line-height: var(--image-caption-line-height);
215
+ font-weight: var(--image-caption-font-weight);
216
+ text-align: center;
217
+ `;
218
+
112
219
  const ColorModeAwareImage = styled.img<{ $colorMode: string; $withLightbox?: boolean }>`
113
220
  html:not(.${(props) => props.$colorMode}) && {
114
221
  display: none;
@@ -121,7 +228,7 @@ const ColorModeAwareImage = styled.img<{ $colorMode: string; $withLightbox?: boo
121
228
  `;
122
229
 
123
230
  const Overlay = styled.div`
124
- background-color: var(--bg-color-modal-overlay);
231
+ background-color: var(--image-lightbox-overlay-bg-color);
125
232
  grid-column: 1 / 2;
126
233
  grid-row: 1 / 2;
127
234
  height: 100%;
@@ -129,6 +236,33 @@ const Overlay = styled.div`
129
236
  z-index: -1;
130
237
  `;
131
238
 
239
+ const CloseButton = styled(Button)`
240
+ position: absolute;
241
+ top: 0;
242
+ left: 100%;
243
+ margin-left: var(--image-lightbox-close-offset);
244
+ width: var(--image-lightbox-close-size);
245
+ height: var(--image-lightbox-close-size);
246
+ border-radius: var(--image-lightbox-close-border-radius);
247
+ z-index: 1;
248
+
249
+ svg {
250
+ width: var(--image-lightbox-close-icon-size);
251
+ height: var(--image-lightbox-close-icon-size);
252
+ }
253
+ `;
254
+
255
+ const LightboxContent = styled.div`
256
+ position: relative;
257
+ grid-column: 1 / 2;
258
+ grid-row: 1 / 2;
259
+ margin: auto;
260
+ width: fit-content;
261
+ height: fit-content;
262
+ max-width: var(--image-lightbox-content-max-width);
263
+ max-height: var(--image-lightbox-content-max-height);
264
+ `;
265
+
132
266
  const LightboxContainer = styled.div`
133
267
  display: grid;
134
268
  height: 100vh;
@@ -144,10 +278,13 @@ const LightboxContainer = styled.div`
144
278
 
145
279
  img {
146
280
  cursor: pointer;
147
- grid-column: 1 / 2;
148
- grid-row: 1 / 2;
149
- margin: auto;
150
- max-width: 90%;
151
- max-height: 90%;
281
+ display: block;
282
+ max-width: var(--image-lightbox-image-max-width);
283
+ max-height: var(--image-lightbox-image-max-height);
284
+ border-radius: var(--image-lightbox-image-border-radius);
285
+ background:
286
+ var(--image-lightbox-image-bg-placeholder) center / cover no-repeat,
287
+ var(--image-frame-bg-color);
288
+ box-shadow: var(--image-lightbox-image-shadow);
152
289
  }
153
290
  `;
@@ -0,0 +1,56 @@
1
+ import { css } from 'styled-components';
2
+
3
+ export const image = css`
4
+ /* === Image === */
5
+
6
+ /**
7
+ * @tokens Image frame
8
+ */
9
+ --image-frame-padding: var(--spacing-xs);
10
+ --image-frame-border-color: var(--border-color-secondary);
11
+ --image-frame-border-radius: var(--border-radius-xxl);
12
+ --image-frame-bg-color: var(--bg-color-raised);
13
+ --image-frame-image-border-radius: var(--border-radius-lg);
14
+
15
+ /**
16
+ * @tokens Image caption
17
+ */
18
+ --image-caption-padding: var(--spacing-sm) var(--spacing-lg) var(--spacing-xxs) var(--spacing-lg);
19
+ --image-caption-text-color: var(--text-color-primary);
20
+ --image-caption-font-size: var(--font-size-base);
21
+ --image-caption-line-height: var(--line-height-base);
22
+ --image-caption-font-weight: var(--font-weight-regular);
23
+
24
+ /**
25
+ * @tokens Image gallery
26
+ */
27
+ --image-gallery-border-radius: var(--border-radius-lg);
28
+ --image-gallery-image-bg-color: var(--bg-color-tonal);
29
+
30
+ /**
31
+ * @tokens Image lightbox
32
+ */
33
+ --image-lightbox-overlay-bg-color: var(--bg-color-modal-overlay);
34
+ --image-lightbox-content-max-width: 90%;
35
+ --image-lightbox-content-max-height: 90%;
36
+
37
+ --image-lightbox-image-max-width: min(
38
+ 90vw,
39
+ calc(100vw - 2 * var(--image-lightbox-side-gutter))
40
+ );
41
+ --image-lightbox-image-max-height: 90vh;
42
+ --image-lightbox-image-border-radius: var(--border-radius-xl);
43
+ --image-lightbox-image-bg-placeholder: var(--bg-color-tonal);
44
+ --image-lightbox-image-shadow: var(--bg-raised-shadow);
45
+
46
+ /**
47
+ * @tokens Image lightbox close button
48
+ */
49
+ --image-lightbox-close-size: 40px;
50
+ --image-lightbox-close-icon-size: 18px;
51
+ --image-lightbox-close-offset: var(--spacing-base);
52
+ --image-lightbox-close-border-radius: var(--border-radius-lg);
53
+ --image-lightbox-side-gutter: calc(
54
+ var(--image-lightbox-close-size) + var(--image-lightbox-close-offset) + var(--spacing-sm)
55
+ );
56
+ `;
@@ -8,6 +8,7 @@ import { breadcrumbs } from '@redocly/theme/components/Breadcrumbs/variables';
8
8
  import { tag } from '@redocly/theme/components/Tag/variables';
9
9
  import { toc } from '@redocly/theme/components/TableOfContent/variables';
10
10
  import { catalog } from '@redocly/theme/components/Catalog/variables';
11
+ import { image } from '@redocly/theme/components/Image/variables';
11
12
  import { filter } from '@redocly/theme/components/Filter/variables';
12
13
  import { catalogClassic } from '@redocly/theme/components/CatalogClassic/variables';
13
14
  import { apiReferencePanels, responsePanelColors } from '@redocly/theme/components/Panel/variables';
@@ -1301,6 +1302,7 @@ export const styles = css<{ palette?: string }>`
1301
1302
  ${footer}
1302
1303
  ${headingsTypography}
1303
1304
  ${httpTag}
1305
+ ${image}
1304
1306
  ${inputs}
1305
1307
  ${languagePicker}
1306
1308
  ${lastUpdated}
@@ -0,0 +1,45 @@
1
+ export type SecurityDetails = {
2
+ password?: string;
3
+ username?: string;
4
+ token?: {
5
+ token_type?: string;
6
+ access_token: string;
7
+ };
8
+ client_id?: string;
9
+ client_secret?: string;
10
+ scopes?: string[];
11
+ };
12
+
13
+ export type ConfigureInputHintAction = {
14
+ label: string;
15
+ action: () => void;
16
+ };
17
+
18
+ export type ConfigureInputHint = {
19
+ title: string;
20
+ text: string;
21
+ actions?: ConfigureInputHintAction[];
22
+ };
23
+
24
+ export type ConfigureRequestValues = {
25
+ headers?: Record<string, string>;
26
+ /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
27
+ body?: Record<string, any>;
28
+ query?: Record<string, string>;
29
+ path?: Record<string, string>;
30
+ cookie?: Record<string, string>;
31
+ security?: Record<string, SecurityDetails>;
32
+ envVariables?: Record<string, string>;
33
+ serverVariables?: Record<string, string>;
34
+ inputHints?: Record<string, ConfigureInputHint>;
35
+ };
36
+
37
+ export type ConfigureServerRequestValues = {
38
+ [serverUrl: string]: ConfigureRequestValues;
39
+ };
40
+
41
+ export type ConfigureReplayConfig =
42
+ | ConfigureRequestValues
43
+ | ConfigureServerRequestValues
44
+ | null
45
+ | undefined;
@@ -1,32 +1,12 @@
1
1
  import type { UserClaims, OpenAPIServer } from '@redocly/theme/core/types';
2
2
 
3
- export type SecurityDetails = {
4
- password?: string;
5
- username?: string;
6
- token?: {
7
- token_type?: string;
8
- access_token: string;
9
- };
10
- client_id?: string;
11
- client_secret?: string;
12
- scopes?: string[];
13
- };
3
+ export * from '@redocly/theme/ext/configure-helpers';
4
+ export * from '@redocly/theme/ext/helpers/is-direct-configure-request-values';
14
5
 
15
- export type ConfigureRequestValues = {
16
- headers?: Record<string, string>;
17
- /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
18
- body?: Record<string, any>;
19
- query?: Record<string, string>;
20
- path?: Record<string, string>;
21
- cookie?: Record<string, string>;
22
- security?: Record<string, SecurityDetails>;
23
- envVariables?: Record<string, string>;
24
- serverVariables?: Record<string, string>;
25
- };
26
-
27
- export type ConfigureServerRequestValues = {
28
- [serverUrl: string]: ConfigureRequestValues;
29
- };
6
+ import type {
7
+ ConfigureRequestValues,
8
+ ConfigureServerRequestValues,
9
+ } from '@redocly/theme/ext/configure-helpers';
30
10
 
31
11
  type Configure = {
32
12
  requestValues?: ConfigureRequestValues | ConfigureServerRequestValues;
@@ -0,0 +1,53 @@
1
+ import type {
2
+ ConfigureRequestValues,
3
+ ConfigureServerRequestValues,
4
+ } from '@redocly/theme/ext/configure-helpers';
5
+
6
+ const CONFIGURE_REQUEST_VALUE_KEYS = [
7
+ 'headers',
8
+ 'body',
9
+ 'query',
10
+ 'path',
11
+ 'cookie',
12
+ 'security',
13
+ 'envVariables',
14
+ 'serverVariables',
15
+ 'inputHints',
16
+ ] as const;
17
+
18
+ const CONFIGURE_REQUEST_VALUE_KEY_SET = new Set<string>(CONFIGURE_REQUEST_VALUE_KEYS);
19
+
20
+ function hasConfigureRequestValueKeys(value: object): boolean {
21
+ return CONFIGURE_REQUEST_VALUE_KEYS.some((key) => key in value);
22
+ }
23
+
24
+ /**
25
+ * Type guard that distinguishes a direct {@link ConfigureRequestValues} payload (request
26
+ * values that apply to every server) from a {@link ConfigureServerRequestValues} payload
27
+ * (request values keyed by server URL).
28
+ *
29
+ * A payload is treated as server-keyed when at least one top-level key is *not* a known
30
+ * request-value key (e.g. `headers`, `security`, `inputHints`) and its value looks like a
31
+ * nested {@link ConfigureRequestValues}. This keeps mixed shapes — e.g. server-keyed values
32
+ * alongside a top-level `inputHints` sibling — classified as server-keyed.
33
+ *
34
+ * @param value - The configure payload returned from `getReplayConfiguration`.
35
+ * @returns `true` when `value` is a direct {@link ConfigureRequestValues}, narrowing the type.
36
+ */
37
+ export function isDirectConfigureRequestValues(
38
+ value: ConfigureRequestValues | ConfigureServerRequestValues,
39
+ ): value is ConfigureRequestValues {
40
+ const hasServerKeyedValues = Object.entries(value).some(
41
+ ([key, entry]) =>
42
+ !CONFIGURE_REQUEST_VALUE_KEY_SET.has(key) &&
43
+ entry != null &&
44
+ typeof entry === 'object' &&
45
+ hasConfigureRequestValueKeys(entry),
46
+ );
47
+
48
+ if (hasServerKeyedValues) {
49
+ return false;
50
+ }
51
+
52
+ return hasConfigureRequestValueKeys(value);
53
+ }
@@ -1,8 +1,8 @@
1
1
  import { useEffect, useState, useCallback } from 'react';
2
2
 
3
- import type {
4
- ConfigureRequestValues,
5
- ConfigureServerRequestValues,
3
+ import {
4
+ type ConfigureRequestValues,
5
+ type ConfigureServerRequestValues,
6
6
  } from '@redocly/theme/ext/configure';
7
7
  import type { UserClaims, OpenAPIServer, OpenAPIInfo } from '@redocly/theme/core/types';
8
8
 
@@ -13,9 +13,20 @@ export const img: MarkdocSchemaWrapper = {
13
13
  type: String,
14
14
  resolver: 'imageSrcSet',
15
15
  },
16
+ images: {
17
+ type: Array,
18
+ resolver: 'imageGallery',
19
+ },
16
20
  alt: {
17
21
  type: String,
18
22
  },
23
+ caption: {
24
+ type: String,
25
+ },
26
+ framed: {
27
+ type: Boolean,
28
+ default: false,
29
+ },
19
30
  withLightbox: {
20
31
  type: Boolean,
21
32
  default: false,
@@ -44,8 +55,31 @@ export const img: MarkdocSchemaWrapper = {
44
55
  },
45
56
  },
46
57
  validate: (node) => {
47
- if (!node.attributes.src && !node.attributes.srcSet) {
48
- return [{ id: '', message: 'src or srcSet is required', level: 'error' }];
58
+ const { src, srcSet, images } = node.attributes;
59
+ const hasGallery = Array.isArray(images) && images.length > 0;
60
+ if (!src && !srcSet && !hasGallery) {
61
+ return [{ id: '', message: 'src, srcSet, or images is required', level: 'error' }];
62
+ }
63
+ if (hasGallery && (src || srcSet)) {
64
+ return [
65
+ {
66
+ id: 'invalid-img-source-combination',
67
+ message: 'images cannot be used together with src or srcSet',
68
+ level: 'error',
69
+ },
70
+ ];
71
+ }
72
+ if (src && srcSet) {
73
+ return [
74
+ {
75
+ id: 'invalid-img-source-combination',
76
+ message: 'src cannot be used together with srcSet',
77
+ level: 'error',
78
+ },
79
+ ];
80
+ }
81
+ if (Array.isArray(images) && images.length > 3) {
82
+ return [{ id: '', message: 'images supports a maximum of 3 items', level: 'error' }];
49
83
  }
50
84
  return [];
51
85
  },