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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/lib/components/Breadcrumbs/BreadcrumbDropdown.d.ts +2 -1
  2. package/lib/components/Breadcrumbs/BreadcrumbDropdown.js +9 -10
  3. package/lib/components/Breadcrumbs/Breadcrumbs.js +5 -22
  4. package/lib/components/Buttons/AIAssistantButton.js +2 -6
  5. package/lib/components/Buttons/EditPageButton.js +2 -1
  6. package/lib/components/Catalog/CatalogCardView/CatalogCard.js +1 -1
  7. package/lib/components/CatalogClassic/CatalogClassicActions.js +3 -1
  8. package/lib/components/CatalogClassic/CatalogClassicCard.js +1 -1
  9. package/lib/components/CatalogClassic/CatalogClassicInfoBlock.js +2 -1
  10. package/lib/components/Dropdown/Dropdown.d.ts +2 -0
  11. package/lib/components/Dropdown/Dropdown.js +8 -2
  12. package/lib/components/Feedback/ReportDialog.js +4 -1
  13. package/lib/components/Filter/FilterCheckboxes.js +3 -1
  14. package/lib/components/Footer/FooterItem.js +1 -1
  15. package/lib/components/LanguagePicker/LanguagePicker.js +3 -1
  16. package/lib/components/Logo/Logo.js +2 -1
  17. package/lib/components/Menu/MenuContainer.js +1 -0
  18. package/lib/components/Menu/MenuItem.js +21 -7
  19. package/lib/components/Navbar/Navbar.js +6 -2
  20. package/lib/components/Navbar/NavbarItem.js +3 -1
  21. package/lib/components/Search/SearchAiMessage.js +23 -5
  22. package/lib/components/Search/SearchDialog.js +5 -33
  23. package/lib/components/Search/SearchInput.js +4 -1
  24. package/lib/components/Search/SearchRecent.js +3 -1
  25. package/lib/components/SidebarActions/SidebarActions.js +10 -3
  26. package/lib/components/TableOfContent/TableOfContent.js +1 -1
  27. package/lib/components/UserMenu/LoginButton.js +2 -1
  28. package/lib/components/UserMenu/LogoutMenuItem.js +2 -1
  29. package/lib/core/hooks/search/use-search-dialog.js +7 -2
  30. package/lib/core/hooks/use-banner-telemetry.js +2 -11
  31. package/lib/core/hooks/use-color-switcher.js +2 -1
  32. package/lib/core/hooks/use-page-actions.js +12 -21
  33. package/lib/core/hooks/use-product-picker.js +14 -4
  34. package/lib/core/hooks/use-telemetry-fallback.d.ts +45 -37
  35. package/lib/core/hooks/use-telemetry-fallback.js +47 -39
  36. package/lib/core/types/search.d.ts +2 -1
  37. package/lib/core/types/search.js +1 -0
  38. package/lib/core/utils/index.d.ts +2 -0
  39. package/lib/core/utils/index.js +2 -0
  40. package/lib/core/utils/telemetry/generate-before-after-context.d.ts +37 -0
  41. package/lib/core/utils/telemetry/generate-before-after-context.js +32 -0
  42. package/lib/core/utils/telemetry/generate-resource-urn.d.ts +7 -0
  43. package/lib/core/utils/telemetry/generate-resource-urn.js +13 -0
  44. package/lib/core/utils/telemetry/get-base-data-attributes.d.ts +14 -0
  45. package/lib/core/utils/telemetry/get-base-data-attributes.js +17 -0
  46. package/package.json +3 -3
  47. package/src/components/Breadcrumbs/BreadcrumbDropdown.tsx +18 -4
  48. package/src/components/Breadcrumbs/Breadcrumbs.tsx +5 -6
  49. package/src/components/Buttons/AIAssistantButton.tsx +2 -3
  50. package/src/components/Buttons/EditPageButton.tsx +4 -1
  51. package/src/components/Catalog/CatalogCardView/CatalogCard.tsx +2 -2
  52. package/src/components/CatalogClassic/CatalogClassicActions.tsx +4 -2
  53. package/src/components/CatalogClassic/CatalogClassicCard.tsx +9 -2
  54. package/src/components/CatalogClassic/CatalogClassicInfoBlock.tsx +2 -1
  55. package/src/components/Dropdown/Dropdown.tsx +8 -1
  56. package/src/components/Feedback/ReportDialog.tsx +4 -1
  57. package/src/components/Filter/FilterCheckboxes.tsx +4 -2
  58. package/src/components/Footer/FooterItem.tsx +4 -2
  59. package/src/components/LanguagePicker/LanguagePicker.tsx +9 -2
  60. package/src/components/Logo/Logo.tsx +7 -1
  61. package/src/components/Menu/MenuContainer.tsx +1 -0
  62. package/src/components/Menu/MenuItem.tsx +35 -4
  63. package/src/components/Navbar/Navbar.tsx +7 -3
  64. package/src/components/Navbar/NavbarItem.tsx +7 -1
  65. package/src/components/Search/SearchAiMessage.tsx +25 -5
  66. package/src/components/Search/SearchDialog.tsx +6 -13
  67. package/src/components/Search/SearchInput.tsx +4 -1
  68. package/src/components/Search/SearchRecent.tsx +4 -2
  69. package/src/components/SidebarActions/SidebarActions.tsx +10 -3
  70. package/src/components/TableOfContent/TableOfContent.tsx +7 -2
  71. package/src/components/UserMenu/LoginButton.tsx +4 -1
  72. package/src/components/UserMenu/LogoutMenuItem.tsx +2 -1
  73. package/src/core/hooks/search/use-search-dialog.ts +13 -2
  74. package/src/core/hooks/use-banner-telemetry.ts +5 -4
  75. package/src/core/hooks/use-color-switcher.ts +7 -1
  76. package/src/core/hooks/use-page-actions.ts +17 -28
  77. package/src/core/hooks/use-product-picker.ts +19 -5
  78. package/src/core/hooks/use-telemetry-fallback.ts +47 -39
  79. package/src/core/types/search.ts +1 -0
  80. package/src/core/utils/index.ts +2 -0
  81. package/src/core/utils/telemetry/generate-before-after-context.ts +59 -0
  82. package/src/core/utils/telemetry/generate-resource-urn.ts +9 -0
  83. package/src/core/utils/telemetry/get-base-data-attributes.ts +27 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@redocly/theme",
3
- "version": "0.67.0-next.3",
3
+ "version": "0.67.0-next.5",
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.2"
66
+ "@redocly/realm-asyncapi-sdk": "0.13.0-next.4"
67
67
  },
68
68
  "dependencies": {
69
69
  "@tanstack/react-query": "5.62.3",
@@ -81,7 +81,7 @@
81
81
  "openapi-sampler": "^1.7.2",
82
82
  "react-calendar": "5.1.0",
83
83
  "react-date-picker": "11.0.0",
84
- "@redocly/config": "0.49.0"
84
+ "@redocly/config": "0.49.1"
85
85
  },
86
86
  "scripts": {
87
87
  "watch": "tsgo -p tsconfig.build.json && (concurrently \"tsgo -w -p tsconfig.build.json\" \"tsc-alias -w -p tsconfig.build.json\")",
@@ -12,6 +12,7 @@ import { Tooltip } from '@redocly/theme/components/Tooltip/Tooltip';
12
12
  import { GenericIcon } from '@redocly/theme/icons/GenericIcon/GenericIcon';
13
13
  import { BREADCRUMB_MAX_LENGTH } from '@redocly/theme/core/constants';
14
14
  import { BreadcrumbIcon } from '@redocly/theme/components/Breadcrumbs/BreadcrumbIcon';
15
+ import { getBaseDataAttributes } from '@redocly/theme/core/utils';
15
16
 
16
17
  type BreadcrumbDropdownProps = {
17
18
  children?: React.ReactNode;
@@ -19,6 +20,7 @@ type BreadcrumbDropdownProps = {
19
20
  items: (BreadcrumbItem & { isActive?: boolean })[];
20
21
  onItemClick?: (item: BreadcrumbItem, index: number) => void;
21
22
  className?: string;
23
+ withChevron?: boolean;
22
24
  };
23
25
 
24
26
  export function BreadcrumbDropdown({
@@ -27,10 +29,12 @@ export function BreadcrumbDropdown({
27
29
  items,
28
30
  onItemClick,
29
31
  className,
32
+ withChevron,
30
33
  }: BreadcrumbDropdownProps): JSX.Element | null {
31
34
  const { useTelemetry, useTranslate } = useThemeHooks();
32
35
  const telemetry = useTelemetry();
33
36
  const { translate } = useTranslate();
37
+ const [isOpen, setIsOpen] = React.useState(false);
34
38
 
35
39
  if (!items || items.length === 0) {
36
40
  return null;
@@ -46,7 +50,12 @@ export function BreadcrumbDropdown({
46
50
  children
47
51
  );
48
52
 
49
- const trigger = <StyledDropdownTrigger>{triggerContent}</StyledDropdownTrigger>;
53
+ const trigger = (
54
+ <StyledDropdownTrigger>
55
+ {triggerContent}
56
+ {withChevron && <GenericIcon icon={isOpen ? 'chevron-up' : 'chevron-down'} />}
57
+ </StyledDropdownTrigger>
58
+ );
50
59
 
51
60
  return (
52
61
  <BreadcrumbDropdownWrapper
@@ -54,7 +63,12 @@ export function BreadcrumbDropdown({
54
63
  className={className}
55
64
  data-testid="breadcrumb-dropdown"
56
65
  >
57
- <Dropdown trigger={trigger} closeOnClick={true}>
66
+ <Dropdown
67
+ trigger={trigger}
68
+ closeOnClick={true}
69
+ onOpen={() => setIsOpen(true)}
70
+ onClose={() => setIsOpen(false)}
71
+ >
58
72
  <DropdownMenu>
59
73
  {items.map((item, index) => {
60
74
  const isActive = Boolean(item?.isActive);
@@ -68,7 +82,7 @@ export function BreadcrumbDropdown({
68
82
  onItemClick?.(item, index);
69
83
  telemetry.sendBreadcrumbClickedMessage([
70
84
  {
71
- object: 'breadcrumb',
85
+ ...getBaseDataAttributes('breadcrumbId', 'breadcrumb'),
72
86
  link: item.link,
73
87
  position: index + 1,
74
88
  totalBreadcrumbs: items.length,
@@ -117,7 +131,7 @@ const StyledDropdownTrigger = styled.button`
117
131
  background-color: var(--breadcrumbs-background-color-hover);
118
132
  }
119
133
 
120
- &:focus {
134
+ &:focus-visible {
121
135
  box-shadow: var(--breadcrumbs-box-shadow-focus);
122
136
  outline: none;
123
137
  }
@@ -8,9 +8,8 @@ import { useThemeHooks } from '@redocly/theme/core/hooks';
8
8
  import { Breadcrumb } from '@redocly/theme/components/Breadcrumbs/Breadcrumb';
9
9
  import { BreadcrumbDropdown } from '@redocly/theme/components/Breadcrumbs/BreadcrumbDropdown';
10
10
  import { BreadcrumbIcon } from '@redocly/theme/components/Breadcrumbs/BreadcrumbIcon';
11
- import { trimText } from '@redocly/theme/core/utils';
11
+ import { trimText, getBaseDataAttributes } 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,10 +64,11 @@ 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
  {
71
- object: 'breadcrumb',
71
+ ...getBaseDataAttributes('breadcrumbId', 'breadcrumb'),
72
72
  link: item.link,
73
73
  position: itemIdx + 1,
74
74
  totalBreadcrumbs: siblingsWithActive.length,
@@ -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
  }
@@ -92,7 +91,7 @@ export function Breadcrumbs(props: {
92
91
  onClick={() =>
93
92
  telemetry.sendBreadcrumbClickedMessage([
94
93
  {
95
- object: 'breadcrumb',
94
+ ...getBaseDataAttributes('breadcrumbId', 'breadcrumb'),
96
95
  link: breadcrumb.link,
97
96
  position: idx + 1,
98
97
  totalBreadcrumbs: breadcrumbs.length,
@@ -120,7 +119,7 @@ export function Breadcrumbs(props: {
120
119
  onItemClick={(item, itemIdx) =>
121
120
  telemetry.sendBreadcrumbClickedMessage([
122
121
  {
123
- object: 'breadcrumb',
122
+ ...getBaseDataAttributes('breadcrumbId', 'breadcrumb'),
124
123
  link: item.link,
125
124
  position: itemIdx + 1,
126
125
  totalBreadcrumbs: breadcrumbs.length,
@@ -9,6 +9,7 @@ import { ChatIcon } from '@redocly/theme/icons/ChatIcon/ChatIcon';
9
9
  import { AiStarsGradientIcon } from '@redocly/theme/icons/AiStarsGradientIcon/AiStarsGradientIcon';
10
10
  import { RedoclyIcon } from '@redocly/theme/icons/RedoclyIcon/RedoclyIcon';
11
11
  import { SearchSessionProvider } from '@redocly/theme/core/contexts';
12
+ import { getBaseDataAttributes } from '@redocly/theme/core/utils';
12
13
 
13
14
  type AIAssistantButtonIconType = 'chat' | 'sparkles' | 'redocly';
14
15
  type AIAssistantButtonType = 'button' | 'icon';
@@ -79,9 +80,7 @@ export function AIAssistantButton() {
79
80
  setIsOpen(true);
80
81
  telemetry.sendSearchAiOpenedMessage([
81
82
  {
82
- id: 'aiAssistantTriggerButton',
83
- object: 'search',
84
- uri: 'urn:redocly:realm:ui:search:aiAssistantTriggerButton',
83
+ ...getBaseDataAttributes('aiAssistantTriggerButton', 'search'),
85
84
  method: 'ai_trigger_button',
86
85
  },
87
86
  ]);
@@ -6,6 +6,7 @@ import type { JSX } from 'react';
6
6
  import { useThemeHooks } from '@redocly/theme/core/hooks';
7
7
  import { EditIcon } from '@redocly/theme/icons/EditIcon/EditIcon';
8
8
  import { Button } from '@redocly/theme/components/Button/Button';
9
+ import { getBaseDataAttributes } from '@redocly/theme/core/utils';
9
10
 
10
11
  export type EditPageButtonProps = {
11
12
  to: string;
@@ -24,7 +25,9 @@ export function EditPageButton({ to }: EditPageButtonProps): JSX.Element {
24
25
  variant="ghost"
25
26
  size="small"
26
27
  icon={<EditIcon />}
27
- onClick={() => telemetry.sendEditPageLinkClickedMessage()}
28
+ onClick={() =>
29
+ telemetry.sendPageEditClickedMessage([getBaseDataAttributes('pageEdit', 'button')])
30
+ }
28
31
  data-translation-key="markdown.editPage.text"
29
32
  >
30
33
  {translate('markdown.editPage.text', 'Edit')}
@@ -11,7 +11,7 @@ import { ArrowRightIcon } from '@redocly/theme/icons/ArrowRightIcon/ArrowRightIc
11
11
  import { ArrowUpRightIcon } from '@redocly/theme/icons/ArrowUpRightIcon/ArrowUpRightIcon';
12
12
  import { CatalogTags } from '@redocly/theme/components/Catalog/CatalogTags';
13
13
  import { CatalogEntityIcon } from '@redocly/theme/components/Catalog/CatalogEntityIcon';
14
- import { getPathPrefix } from '@redocly/theme/core/utils';
14
+ import { getPathPrefix, getBaseDataAttributes } from '@redocly/theme/core/utils';
15
15
 
16
16
  export type CatalogCardProps = {
17
17
  entity: BffCatalogEntity;
@@ -31,7 +31,7 @@ export function CatalogCard({ entity, catalogConfig }: CatalogCardProps): JSX.El
31
31
  window.location.assign(
32
32
  `${pathPrefix}/catalogs/${catalogConfig.slug}/entities/${entity.key}`,
33
33
  );
34
- telemetry.sendCatalogItemClickedMessage();
34
+ telemetry.sendCatalogItemClickedMessage([getBaseDataAttributes('catalogItem', 'card')]);
35
35
  }}
36
36
  >
37
37
  <CardContent>
@@ -4,7 +4,7 @@ import styled from 'styled-components';
4
4
  import type { JSX } from 'react';
5
5
  import type { ResolvedFilter } from '@redocly/theme/core/types';
6
6
 
7
- import { breakpoints } from '@redocly/theme/core/utils';
7
+ import { breakpoints, getBaseDataAttributes } from '@redocly/theme/core/utils';
8
8
  import { useThemeHooks } from '@redocly/theme/core/hooks';
9
9
  import { Button } from '@redocly/theme/components/Button/Button';
10
10
  import { FilterIcon } from '@redocly/theme/icons/FilterIcon/FilterIcon';
@@ -42,7 +42,9 @@ export function CatalogClassicActions(props: CatalogClassicActionsProps): JSX.El
42
42
  iconPosition="left"
43
43
  onClick={() => {
44
44
  onOpenFilter();
45
- telemetry.sendCatalogActionsButtonClickedMessage();
45
+ telemetry.sendCatalogActionsClickedMessage([
46
+ getBaseDataAttributes('catalogActions', 'button'),
47
+ ]);
46
48
  }}
47
49
  data-translation-key="catalog.filters.title"
48
50
  >
@@ -4,7 +4,12 @@ import styled from 'styled-components';
4
4
  import type { JSX } from 'react';
5
5
  import type { CatalogItem } from '@redocly/theme/core/types';
6
6
 
7
- import { slug, capitalize, getScorecardColorVariable } from '@redocly/theme/core/utils';
7
+ import {
8
+ slug,
9
+ capitalize,
10
+ getScorecardColorVariable,
11
+ getBaseDataAttributes,
12
+ } from '@redocly/theme/core/utils';
8
13
  import { useThemeHooks } from '@redocly/theme/core/hooks';
9
14
  import { ArrowRightIcon } from '@redocly/theme/icons/ArrowRightIcon/ArrowRightIcon';
10
15
  import { Link } from '@redocly/theme/components/Link/Link';
@@ -38,7 +43,9 @@ export function CatalogClassicCard({ item }: CatalogClassicCardProps): JSX.Eleme
38
43
  <Link key={item.docsLink || item.link} to={item.docsLink || item.link}>
39
44
  <StyledCard
40
45
  data-component-name="CatalogClassic/CatalogClassicCard"
41
- onClick={() => telemetry.sendCatalogItemClickedMessage()}
46
+ onClick={() =>
47
+ telemetry.sendCatalogItemClickedMessage([getBaseDataAttributes('catalogItem', 'card')])
48
+ }
42
49
  >
43
50
  <CardContent>
44
51
  <CardTitleWrapper>
@@ -7,6 +7,7 @@ import { useThemeHooks } from '@redocly/theme/core/hooks';
7
7
  import { getScorecardColorVariable } from '@redocly/theme/core/utils';
8
8
  import { Tag } from '@redocly/theme/components/Tag/Tag';
9
9
  import { Link } from '@redocly/theme/components/Link/Link';
10
+ import { getBaseDataAttributes } from '@redocly/theme/core/utils';
10
11
 
11
12
  export type CatalogClassicInfoBlockProps = {
12
13
  metadata?: {
@@ -53,7 +54,7 @@ function ScorecardBadge(props: {
53
54
  <Link to={slug}>
54
55
  <Tag
55
56
  onClick={() =>
56
- telemetry.sendScorecardLinkClickedMessage([{ object: 'link', action: 'click' }])
57
+ telemetry.sendScorecardLinkClickedMessage([getBaseDataAttributes('scorecardLink', 'tag')])
57
58
  }
58
59
  withStatusDot
59
60
  statusDotColor={`var(${colorVariable})`}
@@ -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) => {
@@ -8,6 +8,7 @@ import type { SubmitFeedbackParams } from '@redocly/theme/core/types';
8
8
  import { useThemeHooks } from '@redocly/theme/core/hooks';
9
9
  import { Comment } from '@redocly/theme/components/Feedback/Comment';
10
10
  import { Portal } from '@redocly/theme/components/Portal/Portal';
11
+ import { getBaseDataAttributes } from '@redocly/theme/core/utils';
11
12
 
12
13
  export type ReportDialogProps = {
13
14
  location: string;
@@ -47,7 +48,9 @@ export function ReportDialog({
47
48
  path: pathname,
48
49
  lang,
49
50
  });
50
- telemetry.sendCodeSnippetReportedMessage();
51
+ telemetry.sendCodeSnippetReportedMessage([
52
+ getBaseDataAttributes('codeSnippet', 'markdocTag'),
53
+ ]);
51
54
  onSubmit();
52
55
  }}
53
56
  isDialog={true}
@@ -11,7 +11,7 @@ import { FilterOptionLabel } from '@redocly/theme/components/Filter/FilterOption
11
11
  import { useThemeHooks } from '@redocly/theme/core/hooks';
12
12
  import { CheckboxIcon } from '@redocly/theme/icons/CheckboxIcon/CheckboxIcon';
13
13
  import { CounterTag } from '@redocly/theme/components/Tags/CounterTag';
14
- import { changeTextCasing } from '@redocly/theme/core/utils';
14
+ import { changeTextCasing, getBaseDataAttributes } from '@redocly/theme/core/utils';
15
15
 
16
16
  export function FilterCheckboxes({
17
17
  filter,
@@ -36,7 +36,9 @@ export function FilterCheckboxes({
36
36
  role="link"
37
37
  onClick={() => {
38
38
  filter.toggleOption(value);
39
- telemetry.sendFilterCheckboxToggledMessage([{ object: 'checkbox', id }]);
39
+ telemetry.sendFilterCheckboxToggledMessage([
40
+ getBaseDataAttributes('filterId', 'checkbox'),
41
+ ]);
40
42
  }}
41
43
  >
42
44
  <CheckboxIcon
@@ -7,7 +7,7 @@ import type { ResolvedNavItem } from '@redocly/config';
7
7
  import { useThemeHooks } from '@redocly/theme/core/hooks';
8
8
  import { LaunchIcon } from '@redocly/theme/icons/LaunchIcon/LaunchIcon';
9
9
  import { Link } from '@redocly/theme/components/Link/Link';
10
- import { breakpoints } from '@redocly/theme/core/utils';
10
+ import { breakpoints, getBaseDataAttributes } from '@redocly/theme/core/utils';
11
11
  import { GenericIcon } from '@redocly/theme/icons/GenericIcon/GenericIcon';
12
12
 
13
13
  export type FooterItemProps = {
@@ -44,7 +44,9 @@ export function FooterItem({ item, iconsOnly, className }: FooterItemProps): JSX
44
44
  external={item.external}
45
45
  target={item.target}
46
46
  data-testid={item.label}
47
- onClick={() => telemetry.sendFooterItemClickedMessage()}
47
+ onClick={() =>
48
+ telemetry.sendFooterItemClickedMessage([getBaseDataAttributes('footerItem', 'footer')])
49
+ }
48
50
  data-translation-key={item.labelTranslationKey}
49
51
  >
50
52
  {hasIcon ? (
@@ -4,7 +4,7 @@ import styled from 'styled-components';
4
4
  import type { JSX } from 'react';
5
5
 
6
6
  import { DropdownMenu } from '@redocly/theme/components/Dropdown/DropdownMenu';
7
- import { breakpoints } from '@redocly/theme/core/utils';
7
+ import { breakpoints, generateBeforeAfterContext } from '@redocly/theme/core/utils';
8
8
  import { useLanguagePicker, useThemeHooks } from '@redocly/theme/core/hooks';
9
9
  import { GlobalOutlinedIcon } from '@redocly/theme/icons/GlobalOutlinedIcon/GlobalOutlinedIcon';
10
10
  import { Button } from '@redocly/theme/components/Button/Button';
@@ -41,7 +41,14 @@ export function LanguagePicker(props: LanguagePickerProps): JSX.Element | null {
41
41
  languageInsensitive: true,
42
42
  onAction: () => {
43
43
  props.onChangeLanguage(locale.code);
44
- telemetry.sendLanguagePickerLocaleChangedMessage([{ object: 'locale', locale: locale.code }]);
44
+ telemetry.sendLanguagePickerChangedMessage([
45
+ ...generateBeforeAfterContext<'sendLanguagePickerChangedMessage'>(
46
+ 'languagePicker',
47
+ 'dropdown',
48
+ { locale: currentLocale.code },
49
+ { locale: locale.code },
50
+ ),
51
+ ]);
45
52
  },
46
53
  active: locale.code === currentLocale.code,
47
54
  suffix: locale.code === currentLocale.code && <CheckmarkIcon />,
@@ -7,6 +7,7 @@ import type { LogoConfig } from '@redocly/theme/core/types';
7
7
  import { useThemeHooks } from '@redocly/theme/core/hooks';
8
8
  import { Link } from '@redocly/theme/components/Link/Link';
9
9
  import { Image } from '@redocly/theme/components/Image/Image';
10
+ import { getBaseDataAttributes } from '@redocly/theme/core/utils';
10
11
 
11
12
  export type LogoProps = {
12
13
  config: LogoConfig;
@@ -27,7 +28,12 @@ export function Logo({ config, className, ...otherProps }: LogoProps): JSX.Eleme
27
28
  return (
28
29
  <LogoWrapper data-component-name="Logo/Logo" className={className} {...otherProps}>
29
30
  {config.link ? (
30
- <Link to={config.link} onClick={() => telemetry.sendLogoClickedMessage()}>
31
+ <Link
32
+ to={config.link}
33
+ onClick={() =>
34
+ telemetry.sendLogoClickedMessage([getBaseDataAttributes('logo', 'navbar')])
35
+ }
36
+ >
31
37
  {image}
32
38
  </Link>
33
39
  ) : (
@@ -67,6 +67,7 @@ const MenuContainerComponent = styled.div.attrs<{
67
67
  animation-timing-function: ease;
68
68
  position: relative;
69
69
  overflow-y: auto;
70
+ scrollbar-gutter: stable;
70
71
  flex-grow: ${({ $growContent }) => ($growContent ? 1 : 0)};
71
72
  padding-top: var(--menu-container-padding-top);
72
73
  display: ${({ $hidden }) => ($hidden ? 'none' : 'block')};
@@ -11,7 +11,11 @@ import { ChevronDownIcon } from '@redocly/theme/icons/ChevronDownIcon/ChevronDow
11
11
  import { ChevronRightIcon } from '@redocly/theme/icons/ChevronRightIcon/ChevronRightIcon';
12
12
  import { HttpTag } from '@redocly/theme/components/Tags/HttpTag';
13
13
  import { MenuItemType } from '@redocly/theme/core/constants';
14
- import { getMenuItemType, getOperationColor } from '@redocly/theme/core/utils';
14
+ import {
15
+ getBaseDataAttributes,
16
+ getMenuItemType,
17
+ getOperationColor,
18
+ } from '@redocly/theme/core/utils';
15
19
  import { ArrowRightIcon } from '@redocly/theme/icons/ArrowRightIcon/ArrowRightIcon';
16
20
  import { GenericIcon } from '@redocly/theme/icons/GenericIcon/GenericIcon';
17
21
  import { Tag, ContentWrapper } from '@redocly/theme/components/Tag/Tag';
@@ -39,7 +43,7 @@ export function MenuItem(props: React.PropsWithChildren<MenuItemProps>): JSX.Ele
39
43
  const handleOnClick = () => {
40
44
  telemetry.sendSidebarItemClickedMessage([
41
45
  {
42
- object: 'sidebar_item',
46
+ ...getBaseDataAttributes('sidebarItem', 'sidebar'),
43
47
  label: item.label,
44
48
  type: item.type === 'link' || item.type === 'group' ? item.type : undefined,
45
49
  },
@@ -107,7 +111,13 @@ export function MenuItem(props: React.PropsWithChildren<MenuItemProps>): JSX.Ele
107
111
  {name}
108
112
  </SidebarTag>
109
113
  ))}
110
- <span>{translate(item.labelTranslationKey, item.label)}</span>
114
+ <MenuItemLabelText
115
+ $active={item.active}
116
+ $isSeparator={isSeparator}
117
+ data-text={translate(item.labelTranslationKey, item.label)}
118
+ >
119
+ {translate(item.labelTranslationKey, item.label)}
120
+ </MenuItemLabelText>
111
121
  {item.badges
112
122
  ?.filter(({ position }) => position !== 'before')
113
123
  .map(({ name, color, icon }) => (
@@ -308,7 +318,6 @@ const MenuItemLabelWrapper = styled.li<{
308
318
  css`
309
319
  color: ${$deprecated ? 'var(--menu-content-color-disabled)' : 'var(--menu-item-color-active)'};
310
320
  background-color: var(--menu-item-bg-color-active);
311
- font-weight: var(--menu-item-font-weight-active);
312
321
 
313
322
  ${ChevronDownIcon} path {
314
323
  fill: var(--menu-item-color-active);
@@ -386,6 +395,28 @@ const MenuItemLabel = styled.span`
386
395
  }
387
396
  `;
388
397
 
398
+ const MenuItemLabelText = styled.span<{ $active?: boolean; $isSeparator?: boolean }>`
399
+ display: inline-block;
400
+ max-width: 100%;
401
+ font-weight: var(--menu-item-font-weight);
402
+
403
+ &::before {
404
+ content: attr(data-text);
405
+ display: block;
406
+ height: 0;
407
+ overflow: hidden;
408
+ visibility: hidden;
409
+ font-weight: ${({ $isSeparator }) =>
410
+ $isSeparator ? 'var(--menu-item-font-weight)' : 'var(--menu-item-font-weight-active)'};
411
+ }
412
+
413
+ ${({ $active }) =>
414
+ $active &&
415
+ css`
416
+ font-weight: var(--menu-item-font-weight-active);
417
+ `}
418
+ `;
419
+
389
420
  const SidebarTag = styled(Tag)<{ $bgColor?: string; icon?: React.ReactNode }>`
390
421
  ${({ $bgColor }) => $bgColor && `background-color: ${$bgColor};`} /* for backward compatibility */
391
422
  margin-left: 0;
@@ -4,7 +4,7 @@ import styled from 'styled-components';
4
4
  import type { JSX } from 'react';
5
5
  import type { ResolvedConfigLinks } from '@redocly/config';
6
6
 
7
- import { breakpoints } from '@redocly/theme/core/utils';
7
+ import { breakpoints, getBaseDataAttributes } from '@redocly/theme/core/utils';
8
8
  import { useThemeHooks, useThemeConfig, useMobileMenu } from '@redocly/theme/core/hooks';
9
9
  import { NavbarLogo } from '@redocly/theme/components/Navbar/NavbarLogo';
10
10
  import { NavbarMenu } from '@redocly/theme/components/Navbar/NavbarMenu';
@@ -60,11 +60,15 @@ export function Navbar({ className }: NavbarProps): JSX.Element | null {
60
60
  isOpen
61
61
  ? () => {
62
62
  closeMobileMenu();
63
- telemetry.sendMobileMenuButtonCloseClickedMessage();
63
+ telemetry.sendMobileMenuClosedMessage([
64
+ getBaseDataAttributes('mobileMenuClose', 'button'),
65
+ ]);
64
66
  }
65
67
  : () => {
66
68
  openMobileMenu();
67
- telemetry.sendMobileMenuButtonOpenClickedMessage();
69
+ telemetry.sendMobileMenuOpenedMessage([
70
+ getBaseDataAttributes('mobileMenuOpen', 'button'),
71
+ ]);
68
72
  }
69
73
  }
70
74
  icon={isOpen ? <CloseIcon /> : <MenuIcon />}
@@ -12,6 +12,7 @@ import {
12
12
  getPathnameForLocale,
13
13
  withPathPrefix,
14
14
  removeTrailingSlash,
15
+ getBaseDataAttributes,
15
16
  } from '@redocly/theme/core/utils';
16
17
  import { useThemeHooks } from '@redocly/theme/core/hooks';
17
18
  import { LaunchIcon } from '@redocly/theme/icons/LaunchIcon/LaunchIcon';
@@ -48,7 +49,12 @@ export function NavbarItem({ navItem, className }: NavbarItemProps): JSX.Element
48
49
  $active={isActive}
49
50
  className={className}
50
51
  onClick={() =>
51
- telemetry.sendNavbarMenuItemClickedMessage([{ object: 'menu_item', type: item.type }])
52
+ telemetry.sendNavbarMenuItemClickedMessage([
53
+ {
54
+ ...getBaseDataAttributes('navbarItem', 'navbar'),
55
+ type: item.type,
56
+ },
57
+ ])
52
58
  }
53
59
  external={item.external}
54
60
  target={item.target}
@@ -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;