@redocly/theme 0.62.0-custom.0 → 0.62.0-custom.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/lib/components/Buttons/EditPageButton.js +4 -26
  2. package/lib/components/Catalog/CatalogEntity/CatalogEntityHistory/CatalogEntityHistorySidebar.js +2 -2
  3. package/lib/components/Catalog/CatalogEntity/CatalogEntityHistory/CatalogEntityVersionItem.js +6 -13
  4. package/lib/components/Dropdown/Dropdown.js +1 -1
  5. package/lib/components/Dropdown/variables.js +1 -0
  6. package/lib/components/Feedback/Comment.js +17 -4
  7. package/lib/components/Feedback/Mood.js +6 -3
  8. package/lib/components/Feedback/Rating.js +6 -3
  9. package/lib/components/Feedback/Scale.js +6 -3
  10. package/lib/components/Feedback/Sentiment.js +6 -3
  11. package/lib/components/Menu/variables.js +3 -3
  12. package/lib/components/PageActions/PageActions.js +1 -1
  13. package/lib/components/Search/SearchAiMessage.js +7 -3
  14. package/lib/core/constants/feedback.d.ts +2 -0
  15. package/lib/core/constants/feedback.js +6 -0
  16. package/lib/core/constants/index.d.ts +1 -0
  17. package/lib/core/constants/index.js +1 -0
  18. package/lib/core/hooks/use-page-actions.js +7 -4
  19. package/lib/core/openapi/index.d.ts +1 -0
  20. package/lib/core/openapi/index.js +3 -1
  21. package/lib/core/styles/dark.js +11 -0
  22. package/lib/core/styles/global.js +7 -0
  23. package/lib/core/types/l10n.d.ts +1 -1
  24. package/lib/core/utils/transform-revisions-to-version-history.js +13 -20
  25. package/lib/ext/process-scorecard.d.ts +45 -1
  26. package/lib/ext/process-scorecard.js +1 -0
  27. package/lib/icons/DirectionRightIcon/DirectionRightIcon.d.ts +5 -0
  28. package/lib/icons/DirectionRightIcon/DirectionRightIcon.js +24 -0
  29. package/package.json +5 -5
  30. package/src/components/Buttons/EditPageButton.tsx +13 -34
  31. package/src/components/Catalog/CatalogEntity/CatalogEntityHistory/CatalogEntityHistorySidebar.tsx +2 -2
  32. package/src/components/Catalog/CatalogEntity/CatalogEntityHistory/CatalogEntityVersionItem.tsx +5 -21
  33. package/src/components/Dropdown/Dropdown.tsx +1 -1
  34. package/src/components/Dropdown/variables.ts +1 -0
  35. package/src/components/Feedback/Comment.tsx +22 -4
  36. package/src/components/Feedback/Mood.tsx +6 -2
  37. package/src/components/Feedback/Rating.tsx +6 -2
  38. package/src/components/Feedback/Scale.tsx +6 -2
  39. package/src/components/Feedback/Sentiment.tsx +6 -2
  40. package/src/components/Menu/variables.ts +3 -3
  41. package/src/components/PageActions/PageActions.tsx +1 -1
  42. package/src/components/Search/SearchAiMessage.tsx +4 -2
  43. package/src/core/constants/feedback.ts +2 -0
  44. package/src/core/constants/index.ts +1 -0
  45. package/src/core/hooks/use-page-actions.ts +12 -6
  46. package/src/core/openapi/index.ts +1 -0
  47. package/src/core/styles/dark.ts +12 -0
  48. package/src/core/styles/global.ts +7 -0
  49. package/src/core/types/l10n.ts +1 -0
  50. package/src/core/utils/transform-revisions-to-version-history.ts +13 -21
  51. package/src/ext/process-scorecard.ts +46 -1
  52. package/src/icons/DirectionRightIcon/DirectionRightIcon.tsx +35 -0
@@ -1,5 +1,49 @@
1
- import type { CatalogItem } from '../index.js';
1
+ import type { CatalogItem } from '../core/types';
2
+ import type { ReactNode } from 'react';
3
+ export type ScorecardApiTableRow<TScorecard = unknown> = {
4
+ api: CatalogItem;
5
+ scorecard?: TScorecard;
6
+ };
7
+ export type ScorecardApiTableColumnStatus = 'error' | 'warning' | 'success';
8
+ export type ScorecardApiTableColumnDetails<TScorecard = unknown> = {
9
+ hidden?: boolean;
10
+ tabLabel?: string;
11
+ description?: ReactNode | ((row: ScorecardApiTableRow<TScorecard>) => ReactNode);
12
+ render?: (params: {
13
+ row: ScorecardApiTableRow<TScorecard>;
14
+ value: unknown;
15
+ }) => ReactNode;
16
+ status?: ScorecardApiTableColumnStatus | ((params: {
17
+ row: ScorecardApiTableRow<TScorecard>;
18
+ value: unknown;
19
+ }) => ScorecardApiTableColumnStatus);
20
+ statusRule?: 'problem-if-truthy' | 'problem-if-falsy';
21
+ };
22
+ export type ScorecardApiTableColumn<TScorecard = unknown> = {
23
+ id: string;
24
+ header: string;
25
+ placement?: 'beforeLevels' | 'afterLevels';
26
+ size?: number;
27
+ minSize?: number;
28
+ maxSize?: number;
29
+ sortRule?: 'string' | 'number' | 'date';
30
+ sortDescFirst?: boolean;
31
+ getValue?: (row: ScorecardApiTableRow<TScorecard>) => unknown;
32
+ getSortValue?: (row: ScorecardApiTableRow<TScorecard>) => unknown;
33
+ render?: (params: {
34
+ row: ScorecardApiTableRow<TScorecard>;
35
+ value: unknown;
36
+ }) => ReactNode;
37
+ details?: ScorecardApiTableColumnDetails<TScorecard>;
38
+ sortingFn?: (params: {
39
+ rowA: ScorecardApiTableRow<TScorecard>;
40
+ rowB: ScorecardApiTableRow<TScorecard>;
41
+ valueA: unknown;
42
+ valueB: unknown;
43
+ }) => number;
44
+ };
2
45
  export declare function useProcessScorecard(): {
3
46
  processScorecard: <T = any>(scorecard: T, api: CatalogItem) => T;
4
47
  processInfo: <T>(info: T) => T;
48
+ getApiTableColumns: () => ScorecardApiTableColumn[];
5
49
  };
@@ -6,6 +6,7 @@ function useProcessScorecard() {
6
6
  return {
7
7
  processScorecard: (0, react_1.useCallback)((scorecard) => scorecard, []),
8
8
  processInfo: (0, react_1.useCallback)((info) => info, []),
9
+ getApiTableColumns: (0, react_1.useCallback)(() => [], []),
9
10
  };
10
11
  }
11
12
  //# sourceMappingURL=process-scorecard.js.map
@@ -0,0 +1,5 @@
1
+ import React from 'react';
2
+ import type { IconProps } from '../../icons/types';
3
+ export declare const DirectionRightIcon: import("styled-components").StyledComponent<(props: IconProps) => React.JSX.Element, any, {
4
+ 'data-component-name': string;
5
+ }, "data-component-name">;
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.DirectionRightIcon = void 0;
7
+ const react_1 = __importDefault(require("react"));
8
+ const styled_components_1 = __importDefault(require("styled-components"));
9
+ const utils_1 = require("../../core/utils");
10
+ const Icon = (props) => (react_1.default.createElement("svg", Object.assign({ width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", xmlns: "http://www.w3.org/2000/svg" }, props),
11
+ react_1.default.createElement("path", { d: "M9.5 4L8.79295 4.70705L11.0859 7H5C4.73478 7 4.48043 7.10536 4.29289 7.29289C4.10536 7.48043 4 7.73478 4 8V14H5V8H11.0859L8.79295 10.2929L9.5 11L13 7.5L9.5 4Z", fill: "#1A1C21" }),
12
+ react_1.default.createElement("path", { d: "M5 2H4V6H5V2Z", fill: "#1A1C21" })));
13
+ exports.DirectionRightIcon = (0, styled_components_1.default)(Icon).attrs(() => ({
14
+ 'data-component-name': 'icons/DirectionRightIcon/DirectionRightIcon',
15
+ })) `
16
+ path {
17
+ fill: ${({ color }) => (0, utils_1.getCssColorVariable)(color)};
18
+ }
19
+
20
+ height: ${({ size }) => size || '16px'};
21
+ width: ${({ size }) => size || '16px'};
22
+ vertical-align: middle;
23
+ `;
24
+ //# sourceMappingURL=DirectionRightIcon.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@redocly/theme",
3
- "version": "0.62.0-custom.0",
3
+ "version": "0.62.0-custom.2",
4
4
  "description": "Shared UI components lib",
5
5
  "keywords": [
6
6
  "theme",
@@ -62,8 +62,8 @@
62
62
  "typescript": "5.9.3",
63
63
  "vitest": "4.0.10",
64
64
  "vitest-when": "0.6.2",
65
- "webpack": "5.94.0",
66
- "@redocly/realm-asyncapi-sdk": "0.9.0-next.0"
65
+ "webpack": "5.105.2",
66
+ "@redocly/realm-asyncapi-sdk": "0.9.0-next.1"
67
67
  },
68
68
  "dependencies": {
69
69
  "@tanstack/react-query": "5.62.3",
@@ -78,10 +78,10 @@
78
78
  "lodash.debounce": "^4.0.8",
79
79
  "lodash.throttle": "4.1.1",
80
80
  "nprogress": "0.2.0",
81
- "openapi-sampler": "1.6.2",
81
+ "openapi-sampler": "1.7.0",
82
82
  "react-calendar": "5.1.0",
83
83
  "react-date-picker": "11.0.0",
84
- "@redocly/config": "0.43.0-custom.0"
84
+ "@redocly/config": "0.43.0-custom.1"
85
85
  },
86
86
  "scripts": {
87
87
  "watch": "tsc -p tsconfig.build.json && (concurrently \"tsc -w -p tsconfig.build.json\" \"tsc-alias -w -p tsconfig.build.json\")",
@@ -5,7 +5,7 @@ import type { JSX } from 'react';
5
5
 
6
6
  import { useThemeHooks } from '@redocly/theme/core/hooks';
7
7
  import { EditIcon } from '@redocly/theme/icons/EditIcon/EditIcon';
8
- import { Link } from '@redocly/theme/components/Link/Link';
8
+ import { Button } from '@redocly/theme/components/Button/Button';
9
9
 
10
10
  export type EditPageButtonProps = {
11
11
  to: string;
@@ -17,43 +17,22 @@ export function EditPageButton({ to }: EditPageButtonProps): JSX.Element {
17
17
  const telemetry = useTelemetry();
18
18
 
19
19
  return (
20
- <EditPageButtonWrapper
21
- data-component-name="Buttons/EditPageButton"
22
- target="_blank"
23
- to={to}
24
- onClick={() => telemetry.sendEditPageLinkClickedMessage()}
25
- >
26
- <ButtonIcon />
27
- <ButtonText data-translation-key="markdown.editPage.text">
20
+ <EditPageButtonWrapper data-component-name="Buttons/EditPageButton">
21
+ <Button
22
+ to={to}
23
+ external={true}
24
+ variant="ghost"
25
+ size="small"
26
+ icon={<EditIcon />}
27
+ onClick={() => telemetry.sendEditPageLinkClickedMessage()}
28
+ data-translation-key="markdown.editPage.text"
29
+ >
28
30
  {translate('markdown.editPage.text', 'Edit')}
29
- </ButtonText>
31
+ </Button>
30
32
  </EditPageButtonWrapper>
31
33
  );
32
34
  }
33
35
 
34
- const EditPageButtonWrapper = styled(Link)`
35
- height: fit-content;
36
+ const EditPageButtonWrapper = styled.div`
36
37
  margin-left: auto;
37
- display: inline-flex;
38
- color: var(--text-color-secondary);
39
- font-weight: var(--font-weight-bold);
40
- font-size: var(--font-size-base);
41
- font-family: var(--font-family-base);
42
- text-decoration: none;
43
-
44
- &:hover {
45
- color: var(--text-color-primary);
46
- }
47
-
48
- @media print {
49
- display: none;
50
- }
51
- `;
52
-
53
- const ButtonIcon = styled(EditIcon)`
54
- margin-right: 3px;
55
- `;
56
-
57
- const ButtonText = styled.span`
58
- line-height: 14px;
59
38
  `;
@@ -128,10 +128,10 @@ export function CatalogEntityHistorySidebar({
128
128
 
129
129
  const SidebarOverlay = styled.div`
130
130
  position: fixed;
131
- top: var(--navbar-height);
131
+ top: calc(var(--navbar-height) + var(--banner-height, 0));
132
132
  left: 0;
133
133
  width: var(--sidebar-width);
134
- height: calc(100vh - var(--navbar-height));
134
+ height: calc(100vh - var(--navbar-height) - var(--banner-height, 0));
135
135
  background-color: var(--catalog-history-sidebar-bg-color);
136
136
  border-right: 1px solid var(--catalog-history-sidebar-border-color);
137
137
  display: flex;
@@ -53,9 +53,6 @@ export function CatalogEntityVersionItem({
53
53
 
54
54
  const singleRevisionLink = buildRevisionUrl(basePath, group.singleRevisionDate, group.version);
55
55
 
56
- const isClickable = hasRevisions || Boolean(singleRevisionLink);
57
- const isSingleRevisionActive = Boolean(singleRevisionLink && group.isCurrent);
58
-
59
56
  const versionTitle = isNotSpecifiedVersion
60
57
  ? `${translate('catalog.history.version.label', 'Version')}: ${translate('catalog.history.version.notSpecified', 'not specified')}`
61
58
  : `${translate('catalog.history.version.label', 'Version')}: ${group.version}`;
@@ -64,9 +61,7 @@ export function CatalogEntityVersionItem({
64
61
  <VersionButton
65
62
  type="button"
66
63
  onClick={() => hasRevisions && onToggle(group.version)}
67
- $isClickable={isClickable}
68
64
  $isCurrent={group.isCurrent}
69
- $isActive={isSingleRevisionActive}
70
65
  >
71
66
  <VersionIcon $isCurrent={group.isCurrent}>
72
67
  {group.isCurrent ? (
@@ -155,34 +150,23 @@ export function CatalogEntityVersionItem({
155
150
  }
156
151
 
157
152
  const VersionButton = styled.button<{
158
- $isClickable?: boolean;
159
153
  $isCurrent?: boolean;
160
- $isActive?: boolean;
161
154
  }>`
162
155
  all: unset;
163
156
  display: flex;
164
157
  align-items: center;
165
158
  width: calc(100% - var(--catalog-history-sidebar-version-icon-margin-right));
166
159
  padding: var(--catalog-history-sidebar-version-header-padding);
167
- cursor: ${({ $isClickable }) => ($isClickable ? 'pointer' : 'default')};
160
+ cursor: pointer;
168
161
  border-radius: var(--catalog-history-sidebar-version-header-border-radius);
169
162
  transition: 0.2s ease;
170
163
  text-decoration: none;
171
164
  color: inherit;
172
- background-color: ${({ $isActive }) =>
173
- $isActive ? 'var(--catalog-history-sidebar-revision-item-bg-color-active)' : 'transparent'};
165
+ background-color: 'transparent';
174
166
 
175
- ${({ $isClickable, $isActive }) =>
176
- $isClickable &&
177
- `
178
- &:hover {
179
- background-color: ${
180
- $isActive
181
- ? 'var(--catalog-history-sidebar-revision-item-bg-color-active)'
182
- : 'var(--catalog-history-sidebar-version-header-bg-color-hover)'
183
- };
184
- }
185
- `}
167
+ &:hover {
168
+ background-color: var(--catalog-history-sidebar-version-header-bg-color-hover);
169
+ }
186
170
 
187
171
  ${({ $isCurrent }) =>
188
172
  !$isCurrent &&
@@ -142,5 +142,5 @@ const ChildrenWrapper = styled.div<{ placement?: string; alignment?: string; isO
142
142
  right: ${({ alignment }) => (alignment === 'end' ? '0' : 'auto')};
143
143
  display: ${({ isOpen }) => (isOpen ? 'block' : 'none')};
144
144
 
145
- z-index: var(--z-index-raised);
145
+ z-index: var(--dropdown-menu-z-index);
146
146
  `;
@@ -8,6 +8,7 @@ export const dropdown = css`
8
8
  --dropdown-menu-font-weight: var(--font-weight-regular); // @presenter FontWeight
9
9
  --dropdown-menu-line-height: var(--line-height-base); // @presenter LineHeight
10
10
  --dropdown-menu-text-color: var(--text-color-secondary); // @presenter Color
11
+ --dropdown-menu-z-index: var(--z-index-raised); // @presenter ZIndex
11
12
 
12
13
  --dropdown-menu-padding-top: var(--spacing-xxs);
13
14
  --dropdown-menu-min-width: 100px;
@@ -6,6 +6,7 @@ import type { JSX } from 'react';
6
6
  import { useOutsideClick, useThemeHooks } from '@redocly/theme/core/hooks';
7
7
  import { RadioCheckButtonIcon } from '@redocly/theme/icons/RadioCheckButtonIcon/RadioCheckButtonIcon';
8
8
  import { Button } from '@redocly/theme/components/Button/Button';
9
+ import { MAX_CONTEXT_LENGTH } from '@redocly/theme/core/constants';
9
10
 
10
11
  export type CommentProps = {
11
12
  onSubmit: (value: { comment: string }) => void;
@@ -32,18 +33,21 @@ export function Comment({
32
33
  const { label, submitText } = settings || {};
33
34
  const [text, setText] = React.useState('');
34
35
  const [submitValue, setSubmitValue] = React.useState('');
36
+ const [isMaxLengthReached, setIsMaxLengthReached] = React.useState(false);
35
37
  const modalRef = useRef<HTMLDivElement>(null);
36
38
 
37
39
  useOutsideClick(modalRef, onCancel);
38
40
 
39
41
  const send = () => {
40
- if (!text) return;
41
- setSubmitValue(text);
42
- onSubmit({ comment: text });
42
+ const trimmedText = text.trim();
43
+ if (!trimmedText) return;
44
+ setSubmitValue(trimmedText);
45
+ onSubmit({ comment: trimmedText });
43
46
  };
44
47
  const handleTextAreaChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
45
48
  const commentValue = e.target.value;
46
49
  setText(commentValue);
50
+ setIsMaxLengthReached(commentValue.length >= MAX_CONTEXT_LENGTH);
47
51
 
48
52
  if (!standAlone) {
49
53
  onSubmit({ comment: commentValue });
@@ -81,7 +85,15 @@ export function Comment({
81
85
  {label ||
82
86
  translate('feedback.settings.comment.label', 'Please share your feedback with us.')}
83
87
  </Label>
84
- <TextArea rows={3} onChange={handleTextAreaChange} />
88
+ <TextArea rows={3} maxLength={MAX_CONTEXT_LENGTH} onChange={handleTextAreaChange} />
89
+ {isMaxLengthReached && (
90
+ <MaxLengthMessage data-translation-key="feedback.settings.comment.maxLength">
91
+ {translate('feedback.settings.comment.maxLength', {
92
+ maxLength: MAX_CONTEXT_LENGTH,
93
+ defaultValue: `Maximum content length of ${MAX_CONTEXT_LENGTH} characters reached`,
94
+ })}
95
+ </MaxLengthMessage>
96
+ )}
85
97
  {standAlone && (
86
98
  <ButtonsContainer>
87
99
  {onCancel && (
@@ -175,3 +187,9 @@ const ButtonsContainer = styled.div`
175
187
  margin-top: var(--spacing-xs);
176
188
  gap: var(--spacing-xs);
177
189
  `;
190
+
191
+ const MaxLengthMessage = styled.div`
192
+ font-size: var(--font-size-sm);
193
+ color: var(--text-color-secondary);
194
+ margin-top: var(--spacing-xxs);
195
+ `;
@@ -14,6 +14,7 @@ import { Button } from '@redocly/theme/components/Button/Button';
14
14
  import { FaceDissatisfiedIcon } from '@redocly/theme/icons/FaceDissatisfiedIcon/FaceDissatisfiedIcon';
15
15
  import { FaceSatisfiedIcon } from '@redocly/theme/icons/FaceSatisfiedIcon/FaceSatisfiedIcon';
16
16
  import { FaceNeutralIcon } from '@redocly/theme/icons/FaceNeutralIcon/FaceNeutralIcon';
17
+ import { MAX_EMAIL_LENGTH } from '@redocly/theme/core/constants';
17
18
 
18
19
  export enum MOOD_STATES {
19
20
  SATISFIED = 'satisfied',
@@ -135,11 +136,13 @@ export function Mood({ settings, onSubmit, className }: MoodProps): JSX.Element
135
136
  const displayFeedbackEmail = !!score && !optionalEmailSettings?.hide && !userData.isAuthenticated;
136
137
 
137
138
  const onSubmitMoodForm = () => {
139
+ const trimmedComment = comment?.trim() || undefined;
140
+ const trimmedEmail = email?.trim() || undefined;
138
141
  onSubmit({
139
142
  score: remapScore(score),
140
- comment,
143
+ comment: trimmedComment,
141
144
  reasons,
142
- email,
145
+ email: trimmedEmail,
143
146
  });
144
147
  setIsSubmitted(true);
145
148
  };
@@ -255,6 +258,7 @@ export function Mood({ settings, onSubmit, className }: MoodProps): JSX.Element
255
258
  translate('feedback.settings.optionalEmail.placeholder', 'yourname@example.com')
256
259
  }
257
260
  type="email"
261
+ maxLength={MAX_EMAIL_LENGTH}
258
262
  required={!!email}
259
263
  onKeyDown={(e) => {
260
264
  if (e.key === 'Enter') {
@@ -12,6 +12,7 @@ import { RadioCheckButtonIcon } from '@redocly/theme/icons/RadioCheckButtonIcon/
12
12
  import { Comment } from '@redocly/theme/components/Feedback/Comment';
13
13
  import { Stars } from '@redocly/theme/components/Feedback/Stars';
14
14
  import { Button } from '@redocly/theme/components/Button/Button';
15
+ import { MAX_EMAIL_LENGTH } from '@redocly/theme/core/constants';
15
16
 
16
17
  export const FEEDBACK_MAX_RATING = 5;
17
18
 
@@ -64,12 +65,14 @@ export function Rating({ settings, onSubmit, className }: RatingProps): JSX.Elem
64
65
  };
65
66
 
66
67
  const onSubmitRatingForm = () => {
68
+ const trimmedComment = comment?.trim() || undefined;
69
+ const trimmedEmail = email?.trim() || undefined;
67
70
  onSubmit({
68
71
  score,
69
- comment,
72
+ comment: trimmedComment,
70
73
  reasons,
71
74
  max: FEEDBACK_MAX_RATING,
72
- email,
75
+ email: trimmedEmail,
73
76
  });
74
77
  setIsSubmitted(true);
75
78
  };
@@ -168,6 +171,7 @@ export function Rating({ settings, onSubmit, className }: RatingProps): JSX.Elem
168
171
  translate('feedback.settings.optionalEmail.placeholder', 'yourname@example.com')
169
172
  }
170
173
  type="email"
174
+ maxLength={MAX_EMAIL_LENGTH}
171
175
  required={!!email}
172
176
  onKeyDown={(e) => {
173
177
  if (e.key === 'Enter') {
@@ -12,6 +12,7 @@ import { RadioCheckButtonIcon } from '@redocly/theme/icons/RadioCheckButtonIcon/
12
12
  import { Comment } from '@redocly/theme/components/Feedback/Comment';
13
13
  import { Reasons } from '@redocly/theme/components/Feedback/Reasons';
14
14
  import { Button } from '@redocly/theme/components/Button/Button';
15
+ import { MAX_EMAIL_LENGTH } from '@redocly/theme/core/constants';
15
16
 
16
17
  export const MAX_SCALE = 10;
17
18
 
@@ -96,12 +97,14 @@ export function Scale({ settings, onSubmit, className }: ScaleProps): JSX.Elemen
96
97
  };
97
98
 
98
99
  const onSubmitScaleForm = () => {
100
+ const trimmedComment = comment?.trim() || undefined;
101
+ const trimmedEmail = email?.trim() || undefined;
99
102
  onSubmit({
100
103
  score,
101
- comment,
104
+ comment: trimmedComment,
102
105
  reasons,
103
106
  max: MAX_SCALE,
104
- email,
107
+ email: trimmedEmail,
105
108
  });
106
109
  setIsSubmitted(true);
107
110
  };
@@ -193,6 +196,7 @@ export function Scale({ settings, onSubmit, className }: ScaleProps): JSX.Elemen
193
196
  translate('feedback.settings.optionalEmail.placeholder', 'yourname@example.com')
194
197
  }
195
198
  type="email"
199
+ maxLength={MAX_EMAIL_LENGTH}
196
200
  required={!!email}
197
201
  onKeyDown={(e) => {
198
202
  if (e.key === 'Enter') {
@@ -13,6 +13,7 @@ import { Comment } from '@redocly/theme/components/Feedback/Comment';
13
13
  import { Button } from '@redocly/theme/components/Button/Button';
14
14
  import { ThumbDownIcon } from '@redocly/theme/icons/ThumbDownIcon/ThumbDownIcon';
15
15
  import { ThumbUpIcon } from '@redocly/theme/icons/ThumbUpIcon/ThumbUpIcon';
16
+ import { MAX_EMAIL_LENGTH } from '@redocly/theme/core/constants';
16
17
 
17
18
  export type SentimentProps = {
18
19
  onSubmit: (value: {
@@ -117,11 +118,13 @@ export function Sentiment({ settings, onSubmit, className }: SentimentProps): JS
117
118
  };
118
119
 
119
120
  const onSubmitSentimentForm = () => {
121
+ const trimmedComment = comment?.trim() || undefined;
122
+ const trimmedEmail = email?.trim() || undefined;
120
123
  onSubmit({
121
124
  score,
122
- comment,
125
+ comment: trimmedComment,
123
126
  reasons,
124
- email,
127
+ email: trimmedEmail,
125
128
  });
126
129
  setIsSubmitted(true);
127
130
  };
@@ -227,6 +230,7 @@ export function Sentiment({ settings, onSubmit, className }: SentimentProps): JS
227
230
  translate('feedback.settings.optionalEmail.placeholder', 'yourname@example.com')
228
231
  }
229
232
  type="email"
233
+ maxLength={MAX_EMAIL_LENGTH}
230
234
  required={!!email}
231
235
  onKeyDown={(e) => {
232
236
  if (e.key === 'Enter') {
@@ -130,12 +130,12 @@ export const mobileMenu = css`
130
130
  * @tokens Mobile Menu
131
131
  * */
132
132
  /* Fallback for older browsers. dvh accounts for dynamic UI elements like mobile address bars */
133
- --menu-mobile-height: calc(100vh - var(--navbar-height));
134
- --menu-mobile-height: calc(100dvh - var(--navbar-height));
133
+ --menu-mobile-height: calc(100vh - var(--navbar-height) - var(--banner-height));
134
+ --menu-mobile-height: calc(100dvh - var(--navbar-height) - var(--banner-height));
135
135
  --menu-mobile-width: 100%;
136
136
  --menu-mobile-z-index: var(--z-index-raised);
137
137
  --menu-mobile-left: 0;
138
- --menu-mobile-top: var(--navbar-height);
138
+ --menu-mobile-top: calc(var(--navbar-height) + var(--banner-height));
139
139
  --menu-mobile-transition: 0.5s;
140
140
  --menu-mobile-bg: var(--bg-color); // @presenter Color
141
141
  --menu-mobile-margin: var(--menu-mobile-items-margin-top) var(--menu-mobile-margin-horizontal) 0 var(--menu-mobile-margin-horizontal);
@@ -119,7 +119,7 @@ const LinkMenuItem = styled(Link)`
119
119
  `;
120
120
 
121
121
  const StyledDropdown = styled(Dropdown)`
122
- z-index: calc(var(--z-index-raised) - 1);
122
+ --dropdown-menu-z-index: calc(var(--z-index-raised) - 1);
123
123
  `;
124
124
 
125
125
  const StyledDropdownMenu = styled(DropdownMenu)`
@@ -123,11 +123,13 @@ function SearchAiMessageComponent({
123
123
 
124
124
  const toolCallCompletedText =
125
125
  TOOL_CALL_DISPLAY_TEXT[segment.toolCall.name]?.completedText ??
126
- `${translate('search.ai.toolResult.found', 'Found')} ${segment.toolCall.result?.documentCount ?? 0} ${translate('search.ai.toolResult.found.documents', 'documents')}`;
126
+ // `${translate('search.ai.toolResult.found', 'Found')} ${segment.toolCall.result?.documentCount ?? 0} ${translate('search.ai.toolResult.found.documents', 'documents')}`;
127
+ `Executed "${segment.toolCall.name}" tool`;
127
128
 
128
129
  const toolCallInProgressText =
129
130
  TOOL_CALL_DISPLAY_TEXT[segment.toolCall.name]?.inProgressText ??
130
- translate('search.ai.toolCall.searching', 'Searching...');
131
+ // translate('search.ai.toolCall.searching', 'Searching...');
132
+ `Executing "${segment.toolCall.name}" tool...`;
131
133
 
132
134
  const toolCallDisplayText = toolCallCompleted
133
135
  ? toolCallCompletedText
@@ -0,0 +1,2 @@
1
+ export const MAX_EMAIL_LENGTH = 254;
2
+ export const MAX_CONTEXT_LENGTH = 5000;
@@ -6,3 +6,4 @@ export * from './catalog';
6
6
  export * from './breadcrumb';
7
7
  export * from './request-methods';
8
8
  export * from './mcp';
9
+ export * from './feedback';
@@ -16,6 +16,12 @@ import { useMCPConfig } from './use-mcp-config';
16
16
  import { ClipboardService } from '../utils/clipboard-service';
17
17
  import { IS_BROWSER } from '../utils/dom';
18
18
  import { generateMCPDeepLink } from '../utils/mcp';
19
+ import {
20
+ addTrailingSlash,
21
+ combineUrls,
22
+ removeTrailingSlash,
23
+ withoutPathPrefix,
24
+ } from '../utils/urls';
19
25
 
20
26
  function createPageActionResource(pageSlug: string, pageUrl: string) {
21
27
  return {
@@ -110,11 +116,12 @@ export function usePageActions(
110
116
  const origin = IS_BROWSER
111
117
  ? window.location.origin
112
118
  : ((globalThis as { SSR_HOSTNAME?: string })['SSR_HOSTNAME'] ?? '');
113
- const normalizedSlug = pageSlug.startsWith('/') ? pageSlug : '/' + pageSlug;
114
- const pageUrl = `${origin}${normalizedSlug}`;
115
- const mdPageUrl = new URL(
116
- origin + normalizedSlug + (normalizedSlug === '/' ? 'index.html.md' : '.md'),
117
- ).toString();
119
+ const pathname = addTrailingSlash(pageSlug);
120
+ const pageUrl = combineUrls(origin, pathname);
121
+ const isRoot = withoutPathPrefix(pathname) === '/';
122
+ const mdPageUrl = isRoot
123
+ ? combineUrls(origin, pathname, 'index.html.md')
124
+ : combineUrls(origin, removeTrailingSlash(pathname) + '.md');
118
125
 
119
126
  const actionHandlers: Record<PageActionType, () => PageAction | null> = {
120
127
  'docs-mcp-cursor': createMCPHandler('cursor', false),
@@ -160,7 +167,6 @@ export function usePageActions(
160
167
  action_type: 'view',
161
168
  },
162
169
  ]);
163
- window.location.href = mdPageUrl;
164
170
  },
165
171
  }),
166
172
 
@@ -39,3 +39,4 @@ export { isUndefined, isString, isNotNull, isObject } from '../utils/type-guards
39
39
  export { ThemeDataContext, type ThemeDataTransferObject } from '../contexts/ThemeDataContext';
40
40
  export { SearchSessionProvider, SearchSessionContext } from '../contexts/SearchContext';
41
41
  export { useUniqueSvgIds } from '../hooks/use-unique-svg-ids';
42
+ export { trimText } from '../utils/text-trimmer';
@@ -46,6 +46,17 @@ const replayDarkMode = css`
46
46
  // @tokens End
47
47
  `;
48
48
 
49
+ const badgesDarkMode = css`
50
+ /**
51
+ * @tokens Audience Badge
52
+ */
53
+
54
+ --badge-audience-text-color: var(--text-color-secondary); // @presenter Color
55
+ --badge-audience-bg-color: var(--color-warm-grey-4); // @presenter Color
56
+
57
+ // @tokens End
58
+ `
59
+
49
60
 
50
61
  export const darkMode = css`
51
62
  /* === Dark Theme Colors === */
@@ -334,6 +345,7 @@ export const darkMode = css`
334
345
  ${bannerDarkMode}
335
346
  ${admonitionDarkMode}
336
347
  ${svgViewerDarkMode}
348
+ ${badgesDarkMode}
337
349
 
338
350
  /**
339
351
  * @tokens Dark Theme Scrollbar Config
@@ -823,6 +823,13 @@ const badges = css`
823
823
  --badge-deprecated-bg-color: var(--color-warning-base); // @presenter Color
824
824
  --badge-deprecated-border-radius: var(--border-radius); // @presenter BorderRadius
825
825
 
826
+ /**
827
+ * @tokens Audience Badge
828
+ */
829
+
830
+ --badge-audience-text-color: var(--text-color-secondary); // @presenter Color
831
+ --badge-audience-bg-color: var(--color-warm-grey-2); // @presenter Color
832
+
826
833
  // @tokens End
827
834
  `;
828
835
 
@@ -203,6 +203,7 @@ export type TranslationKey =
203
203
  | 'feedback.settings.comment.label'
204
204
  | 'feedback.settings.comment.send'
205
205
  | 'feedback.settings.comment.cancel'
206
+ | 'feedback.settings.comment.maxLength'
206
207
  | 'feedback.settings.comment.satisfiedLabel'
207
208
  | 'feedback.settings.comment.neutralLabel'
208
209
  | 'feedback.settings.comment.dissatisfiedLabel'