@redocly/theme 0.67.0-next.2 → 0.67.0-next.4

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.
@@ -9,6 +9,7 @@ type BreadcrumbDropdownProps = {
9
9
  })[];
10
10
  onItemClick?: (item: BreadcrumbItem, index: number) => void;
11
11
  className?: string;
12
+ withChevron?: boolean;
12
13
  };
13
- export declare function BreadcrumbDropdown({ children, label, items, onItemClick, className }: BreadcrumbDropdownProps): JSX.Element | null;
14
+ export declare function BreadcrumbDropdown({ children, label, items, onItemClick, className, withChevron }: BreadcrumbDropdownProps): JSX.Element | null;
14
15
  export {};
@@ -47,19 +47,22 @@ const Tooltip_1 = require("../../components/Tooltip/Tooltip");
47
47
  const GenericIcon_1 = require("../../icons/GenericIcon/GenericIcon");
48
48
  const constants_1 = require("../../core/constants");
49
49
  const BreadcrumbIcon_1 = require("../../components/Breadcrumbs/BreadcrumbIcon");
50
- function BreadcrumbDropdown({ children, label, items, onItemClick, className, }) {
50
+ function BreadcrumbDropdown({ children, label, items, onItemClick, className, withChevron, }) {
51
51
  const { useTelemetry, useTranslate } = (0, hooks_1.useThemeHooks)();
52
52
  const telemetry = useTelemetry();
53
53
  const { translate } = useTranslate();
54
+ const [isOpen, setIsOpen] = react_1.default.useState(false);
54
55
  if (!items || items.length === 0) {
55
56
  return null;
56
57
  }
57
58
  const isTruncated = label.length > constants_1.BREADCRUMB_MAX_LENGTH;
58
59
  const triggerContent = isTruncated ? (react_1.default.createElement(Tooltip_1.Tooltip, { tip: label, placement: "bottom" },
59
60
  react_1.default.createElement(TriggerContentWrapper, null, children))) : (children);
60
- const trigger = react_1.default.createElement(StyledDropdownTrigger, null, triggerContent);
61
+ const trigger = (react_1.default.createElement(StyledDropdownTrigger, null,
62
+ triggerContent,
63
+ withChevron && react_1.default.createElement(GenericIcon_1.GenericIcon, { icon: isOpen ? 'chevron-up' : 'chevron-down' })));
61
64
  return (react_1.default.createElement(BreadcrumbDropdownWrapper, { "data-component-name": "Breadcrumbs/BreadcrumbDropdown", className: className, "data-testid": "breadcrumb-dropdown" },
62
- react_1.default.createElement(Dropdown_1.Dropdown, { trigger: trigger, closeOnClick: true },
65
+ react_1.default.createElement(Dropdown_1.Dropdown, { trigger: trigger, closeOnClick: true, onOpen: () => setIsOpen(true), onClose: () => setIsOpen(false) },
63
66
  react_1.default.createElement(DropdownMenu_1.DropdownMenu, null, items.map((item, index) => {
64
67
  const isActive = Boolean(item === null || item === void 0 ? void 0 : item.isActive);
65
68
  const hasLink = Boolean(item.link);
@@ -101,7 +104,7 @@ const StyledDropdownTrigger = styled_components_1.default.button `
101
104
  background-color: var(--breadcrumbs-background-color-hover);
102
105
  }
103
106
 
104
- &:focus {
107
+ &:focus-visible {
105
108
  box-shadow: var(--breadcrumbs-box-shadow-focus);
106
109
  outline: none;
107
110
  }
@@ -12,7 +12,6 @@ const BreadcrumbDropdown_1 = require("../../components/Breadcrumbs/BreadcrumbDro
12
12
  const BreadcrumbIcon_1 = require("../../components/Breadcrumbs/BreadcrumbIcon");
13
13
  const utils_1 = require("../../core/utils");
14
14
  const constants_1 = require("../../core/constants");
15
- const GenericIcon_1 = require("../../icons/GenericIcon/GenericIcon");
16
15
  function Breadcrumbs(props) {
17
16
  const { useBreadcrumbs, useTelemetry, useTranslate } = (0, hooks_1.useThemeHooks)();
18
17
  const { breadcrumbs: fileBreadcrumbs, currentItemSiblings } = useBreadcrumbs();
@@ -46,7 +45,7 @@ function Breadcrumbs(props) {
46
45
  ...currentItemSiblings,
47
46
  ];
48
47
  const translatedLabel = translate(breadcrumb.labelTranslationKey, breadcrumb.label);
49
- return (react_1.default.createElement(BreadcrumbDropdown_1.BreadcrumbDropdown, { label: translatedLabel, items: siblingsWithActive, onItemClick: (item, itemIdx) => telemetry.sendBreadcrumbClickedMessage([
48
+ return (react_1.default.createElement(BreadcrumbDropdown_1.BreadcrumbDropdown, { label: translatedLabel, items: siblingsWithActive, withChevron: true, onItemClick: (item, itemIdx) => telemetry.sendBreadcrumbClickedMessage([
50
49
  {
51
50
  object: 'breadcrumb',
52
51
  link: item.link,
@@ -55,8 +54,7 @@ function Breadcrumbs(props) {
55
54
  },
56
55
  ]) },
57
56
  react_1.default.createElement(BreadcrumbIcon_1.BreadcrumbIcon, { icon: breadcrumb.icon }),
58
- (0, utils_1.trimText)(translatedLabel, constants_1.BREADCRUMB_MAX_LENGTH),
59
- react_1.default.createElement(GenericIcon_1.GenericIcon, { icon: "chevron-down" })));
57
+ (0, utils_1.trimText)(translatedLabel, constants_1.BREADCRUMB_MAX_LENGTH)));
60
58
  }
61
59
  return (react_1.default.createElement(Breadcrumb_1.Breadcrumb, { link: breadcrumb.link, label: translate(breadcrumb.labelTranslationKey, breadcrumb.label), isActive: isActive, icon: breadcrumb.icon, onClick: () => telemetry.sendBreadcrumbClickedMessage([
62
60
  {
@@ -12,6 +12,7 @@ export type DropdownProps = PropsWithChildren<{
12
12
  withArrow?: boolean;
13
13
  onClick?: (event: React.UIEvent) => void;
14
14
  onClose?: () => void;
15
+ onOpen?: () => void;
15
16
  }>;
16
17
  export declare const Dropdown: React.ForwardRefExoticComponent<{
17
18
  trigger: React.ReactNode;
@@ -25,6 +26,7 @@ export declare const Dropdown: React.ForwardRefExoticComponent<{
25
26
  withArrow?: boolean;
26
27
  onClick?: (event: React.UIEvent) => void;
27
28
  onClose?: () => void;
29
+ onOpen?: () => void;
28
30
  } & {
29
31
  children?: React.ReactNode | undefined;
30
32
  } & React.RefAttributes<HTMLDivElement>>;
@@ -42,11 +42,12 @@ const styled_components_1 = __importDefault(require("styled-components"));
42
42
  const hooks_1 = require("../../core/hooks");
43
43
  const ChevronDownIcon_1 = require("../../icons/ChevronDownIcon/ChevronDownIcon");
44
44
  const ChevronUpIcon_1 = require("../../icons/ChevronUpIcon/ChevronUpIcon");
45
- exports.Dropdown = (0, react_1.forwardRef)(({ children, className, active, trigger, triggerEvent = 'click', closeOnClick = true, withArrow, dataAttributes, placement, alignment, onClick, onClose, }, ref) => {
45
+ exports.Dropdown = (0, react_1.forwardRef)(({ children, className, active, trigger, triggerEvent = 'click', closeOnClick = true, withArrow, dataAttributes, placement, alignment, onClick, onClose, onOpen, }, ref) => {
46
46
  const dropdownRef = (0, react_1.useRef)(null);
47
47
  const [isOpen, setIsOpen] = (0, hooks_1.useControlledState)(false, active);
48
48
  const handleOpen = () => {
49
49
  setIsOpen(true);
50
+ onOpen === null || onOpen === void 0 ? void 0 : onOpen();
50
51
  };
51
52
  const handleClose = () => {
52
53
  setIsOpen(false);
@@ -58,7 +59,12 @@ exports.Dropdown = (0, react_1.forwardRef)(({ children, className, active, trigg
58
59
  const handleToggle = (event) => {
59
60
  event.stopPropagation();
60
61
  event.preventDefault();
61
- setIsOpen(!isOpen);
62
+ if (isOpen) {
63
+ handleClose();
64
+ }
65
+ else {
66
+ handleOpen();
67
+ }
62
68
  };
63
69
  const handleKeyDown = (event) => {
64
70
  if (event.key === 'Enter' || event.key === ' ') {
@@ -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
@@ -41,6 +41,7 @@ const react_1 = __importStar(require("react"));
41
41
  const styled_components_1 = __importDefault(require("styled-components"));
42
42
  const types_1 = require("../../core/types");
43
43
  const constants_1 = require("../../core/constants");
44
+ const types_2 = require("../../core/types");
44
45
  const Link_1 = require("../../components/Link/Link");
45
46
  const Tag_1 = require("../../components/Tag/Tag");
46
47
  const constants_2 = require("../../core/constants");
@@ -56,11 +57,28 @@ function MarkdownSegment({ text }) {
56
57
  const markdown = useMarkdownText(text);
57
58
  return react_1.default.createElement(ResponseText, { as: "div", children: markdown, "data-testid": "response-text" });
58
59
  }
59
- function getToolCallDisplayText(toolName) {
60
+ // The codemode `execute` tool passes a human-readable `description` of what its code
61
+ // does; show that instead of a generic "Executing execute..." label.
62
+ function getExecuteDescription(args) {
63
+ if (args && typeof args === 'object' && 'description' in args) {
64
+ const { description } = args;
65
+ if (typeof description === 'string' && description.trim().length > 0) {
66
+ return description.trim();
67
+ }
68
+ }
69
+ return undefined;
70
+ }
71
+ function getToolCallDisplayText(toolCall) {
60
72
  var _a;
61
- return ((_a = constants_1.TOOL_CALL_DISPLAY_TEXT[toolName]) !== null && _a !== void 0 ? _a : {
62
- inProgressText: `Executing ${toolName}...`,
63
- completedText: `${toolName} executed`,
73
+ if (toolCall.name === types_2.ToolCallName.Execute) {
74
+ const description = getExecuteDescription(toolCall.args);
75
+ if (description) {
76
+ return { inProgressText: description, completedText: description };
77
+ }
78
+ }
79
+ return ((_a = constants_1.TOOL_CALL_DISPLAY_TEXT[toolCall.name]) !== null && _a !== void 0 ? _a : {
80
+ inProgressText: `Executing ${toolCall.name}...`,
81
+ completedText: `${toolCall.name} executed`,
64
82
  });
65
83
  }
66
84
  function SearchAiMessageComponent({ role, content, isThinking, resources, className, messageId, feedback, onFeedbackChange, toolCalls = [], contentSegments = [{ type: 'text', text: content }], }) {
@@ -111,7 +129,7 @@ function SearchAiMessageComponent({ role, content, isThinking, resources, classN
111
129
  contentSegments.map((segment, index) => {
112
130
  if (segment.type === 'tool') {
113
131
  const toolCallCompleted = Boolean(segment.toolCall.result);
114
- const { inProgressText, completedText } = getToolCallDisplayText(segment.toolCall.name);
132
+ const { inProgressText, completedText } = getToolCallDisplayText(segment.toolCall);
115
133
  const toolCallDisplayText = toolCallCompleted ? completedText : inProgressText;
116
134
  return (react_1.default.createElement(ToolCallsInfoWrapper, { key: `tool-${index}`, "data-testid": "tool-calls-info" },
117
135
  react_1.default.createElement(ToolCallInfoItem, null,
@@ -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);
@@ -124,5 +124,6 @@ export declare enum ToolCallName {
124
124
  GetEndpoints = "get-endpoints",
125
125
  GetEndpointInfo = "get-endpoint-info",
126
126
  GetSecuritySchemes = "get-security-schemes",
127
- GetFullApiDescription = "get-full-api-description"
127
+ GetFullApiDescription = "get-full-api-description",
128
+ Execute = "execute"
128
129
  }
@@ -14,5 +14,6 @@ var ToolCallName;
14
14
  ToolCallName["GetEndpointInfo"] = "get-endpoint-info";
15
15
  ToolCallName["GetSecuritySchemes"] = "get-security-schemes";
16
16
  ToolCallName["GetFullApiDescription"] = "get-full-api-description";
17
+ ToolCallName["Execute"] = "execute";
17
18
  })(ToolCallName || (exports.ToolCallName = ToolCallName = {}));
18
19
  //# sourceMappingURL=search.js.map
@@ -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.2",
3
+ "version": "0.67.0-next.4",
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.1"
66
+ "@redocly/realm-asyncapi-sdk": "0.13.0-next.3"
67
67
  },
68
68
  "dependencies": {
69
69
  "@tanstack/react-query": "5.62.3",
@@ -19,6 +19,7 @@ type BreadcrumbDropdownProps = {
19
19
  items: (BreadcrumbItem & { isActive?: boolean })[];
20
20
  onItemClick?: (item: BreadcrumbItem, index: number) => void;
21
21
  className?: string;
22
+ withChevron?: boolean;
22
23
  };
23
24
 
24
25
  export function BreadcrumbDropdown({
@@ -27,10 +28,12 @@ export function BreadcrumbDropdown({
27
28
  items,
28
29
  onItemClick,
29
30
  className,
31
+ withChevron,
30
32
  }: BreadcrumbDropdownProps): JSX.Element | null {
31
33
  const { useTelemetry, useTranslate } = useThemeHooks();
32
34
  const telemetry = useTelemetry();
33
35
  const { translate } = useTranslate();
36
+ const [isOpen, setIsOpen] = React.useState(false);
34
37
 
35
38
  if (!items || items.length === 0) {
36
39
  return null;
@@ -46,7 +49,12 @@ export function BreadcrumbDropdown({
46
49
  children
47
50
  );
48
51
 
49
- const trigger = <StyledDropdownTrigger>{triggerContent}</StyledDropdownTrigger>;
52
+ const trigger = (
53
+ <StyledDropdownTrigger>
54
+ {triggerContent}
55
+ {withChevron && <GenericIcon icon={isOpen ? 'chevron-up' : 'chevron-down'} />}
56
+ </StyledDropdownTrigger>
57
+ );
50
58
 
51
59
  return (
52
60
  <BreadcrumbDropdownWrapper
@@ -54,7 +62,12 @@ export function BreadcrumbDropdown({
54
62
  className={className}
55
63
  data-testid="breadcrumb-dropdown"
56
64
  >
57
- <Dropdown trigger={trigger} closeOnClick={true}>
65
+ <Dropdown
66
+ trigger={trigger}
67
+ closeOnClick={true}
68
+ onOpen={() => setIsOpen(true)}
69
+ onClose={() => setIsOpen(false)}
70
+ >
58
71
  <DropdownMenu>
59
72
  {items.map((item, index) => {
60
73
  const isActive = Boolean(item?.isActive);
@@ -117,7 +130,7 @@ const StyledDropdownTrigger = styled.button`
117
130
  background-color: var(--breadcrumbs-background-color-hover);
118
131
  }
119
132
 
120
- &:focus {
133
+ &:focus-visible {
121
134
  box-shadow: var(--breadcrumbs-box-shadow-focus);
122
135
  outline: none;
123
136
  }
@@ -10,7 +10,6 @@ import { BreadcrumbDropdown } from '@redocly/theme/components/Breadcrumbs/Breadc
10
10
  import { BreadcrumbIcon } from '@redocly/theme/components/Breadcrumbs/BreadcrumbIcon';
11
11
  import { trimText } from '@redocly/theme/core/utils';
12
12
  import { BREADCRUMB_MAX_LENGTH } from '@redocly/theme/core/constants';
13
- import { GenericIcon } from '@redocly/theme/icons/GenericIcon/GenericIcon';
14
13
 
15
14
  export function Breadcrumbs(props: {
16
15
  className?: string;
@@ -65,6 +64,7 @@ export function Breadcrumbs(props: {
65
64
  <BreadcrumbDropdown
66
65
  label={translatedLabel}
67
66
  items={siblingsWithActive}
67
+ withChevron
68
68
  onItemClick={(item, itemIdx) =>
69
69
  telemetry.sendBreadcrumbClickedMessage([
70
70
  {
@@ -78,7 +78,6 @@ export function Breadcrumbs(props: {
78
78
  >
79
79
  <BreadcrumbIcon icon={breadcrumb.icon} />
80
80
  {trimText(translatedLabel, BREADCRUMB_MAX_LENGTH)}
81
- <GenericIcon icon="chevron-down" />
82
81
  </BreadcrumbDropdown>
83
82
  );
84
83
  }
@@ -30,6 +30,7 @@ export type DropdownProps = PropsWithChildren<{
30
30
 
31
31
  onClick?: (event: React.UIEvent) => void;
32
32
  onClose?: () => void;
33
+ onOpen?: () => void;
33
34
  }>;
34
35
 
35
36
  export const Dropdown = forwardRef<HTMLDivElement, DropdownProps>(
@@ -47,6 +48,7 @@ export const Dropdown = forwardRef<HTMLDivElement, DropdownProps>(
47
48
  alignment,
48
49
  onClick,
49
50
  onClose,
51
+ onOpen,
50
52
  },
51
53
  ref,
52
54
  ) => {
@@ -55,6 +57,7 @@ export const Dropdown = forwardRef<HTMLDivElement, DropdownProps>(
55
57
 
56
58
  const handleOpen = () => {
57
59
  setIsOpen(true);
60
+ onOpen?.();
58
61
  };
59
62
 
60
63
  const handleClose = () => {
@@ -69,7 +72,11 @@ export const Dropdown = forwardRef<HTMLDivElement, DropdownProps>(
69
72
  const handleToggle = (event: React.UIEvent) => {
70
73
  event.stopPropagation();
71
74
  event.preventDefault();
72
- setIsOpen(!isOpen);
75
+ if (isOpen) {
76
+ handleClose();
77
+ } else {
78
+ handleOpen();
79
+ }
73
80
  };
74
81
 
75
82
  const handleKeyDown = (event: React.KeyboardEvent) => {
@@ -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
+ `;
@@ -10,6 +10,7 @@ import {
10
10
  type ContentSegment,
11
11
  } from '@redocly/theme/core/types';
12
12
  import { TOOL_CALL_DISPLAY_TEXT } from '@redocly/theme/core/constants';
13
+ import { ToolCallName } from '@redocly/theme/core/types';
13
14
  import { Link } from '@redocly/theme/components/Link/Link';
14
15
  import { Tag } from '@redocly/theme/components/Tag/Tag';
15
16
  import { AiSearchConversationRole } from '@redocly/theme/core/constants';
@@ -26,14 +27,33 @@ function MarkdownSegment({ text }: { text: string }): JSX.Element {
26
27
  const markdown = useMarkdownText(text);
27
28
  return <ResponseText as="div" children={markdown} data-testid="response-text" />;
28
29
  }
29
- function getToolCallDisplayText(toolName: string): {
30
+ // The codemode `execute` tool passes a human-readable `description` of what its code
31
+ // does; show that instead of a generic "Executing execute..." label.
32
+ function getExecuteDescription(args: unknown): string | undefined {
33
+ if (args && typeof args === 'object' && 'description' in args) {
34
+ const { description } = args as { description?: unknown };
35
+ if (typeof description === 'string' && description.trim().length > 0) {
36
+ return description.trim();
37
+ }
38
+ }
39
+ return undefined;
40
+ }
41
+
42
+ function getToolCallDisplayText(toolCall: ToolCall): {
30
43
  inProgressText: string;
31
44
  completedText: string;
32
45
  } {
46
+ if (toolCall.name === ToolCallName.Execute) {
47
+ const description = getExecuteDescription(toolCall.args);
48
+ if (description) {
49
+ return { inProgressText: description, completedText: description };
50
+ }
51
+ }
52
+
33
53
  return (
34
- TOOL_CALL_DISPLAY_TEXT[toolName] ?? {
35
- inProgressText: `Executing ${toolName}...`,
36
- completedText: `${toolName} executed`,
54
+ TOOL_CALL_DISPLAY_TEXT[toolCall.name] ?? {
55
+ inProgressText: `Executing ${toolCall.name}...`,
56
+ completedText: `${toolCall.name} executed`,
37
57
  }
38
58
  );
39
59
  }
@@ -135,7 +155,7 @@ function SearchAiMessageComponent({
135
155
  const toolCallCompleted = Boolean(segment.toolCall.result);
136
156
 
137
157
  const { inProgressText, completedText } = getToolCallDisplayText(
138
- segment.toolCall.name,
158
+ segment.toolCall,
139
159
  );
140
160
 
141
161
  const toolCallDisplayText = toolCallCompleted ? completedText : inProgressText;
@@ -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}
@@ -132,4 +132,5 @@ export enum ToolCallName {
132
132
  GetEndpointInfo = 'get-endpoint-info',
133
133
  GetSecuritySchemes = 'get-security-schemes',
134
134
  GetFullApiDescription = 'get-full-api-description',
135
+ Execute = 'execute',
135
136
  }
@@ -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
  },