@redocly/theme 0.70.0-next.2 → 0.70.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.
Files changed (155) hide show
  1. package/lib/components/Catalog/Catalog.js +1 -1
  2. package/lib/components/Catalog/CatalogEntity/CatalogEntityHistory/CatalogEntityHistorySidebar.js +2 -2
  3. package/lib/components/Catalog/CatalogEntity/CatalogEntitySchema.js +1 -1
  4. package/lib/components/Catalog/CatalogMobileFiltersPanel/CatalogMobileFiltersPanel.js +1 -1
  5. package/lib/components/Catalog/CatalogMobileFiltersPanel/CatalogMobileFiltersPanelOverlay.js +2 -2
  6. package/lib/components/CodeBlock/CodeBlockControls.js +3 -5
  7. package/lib/components/Consent/Consent.d.ts +2 -0
  8. package/lib/components/Consent/Consent.js +17 -0
  9. package/lib/components/Consent/ConsentBanner.d.ts +2 -0
  10. package/lib/components/Consent/ConsentBanner.js +120 -0
  11. package/lib/components/Consent/ConsentDialog.d.ts +2 -0
  12. package/lib/components/Consent/ConsentDialog.js +128 -0
  13. package/lib/components/Consent/ConsentSettingsLink.d.ts +2 -0
  14. package/lib/components/Consent/ConsentSettingsLink.js +35 -0
  15. package/lib/components/Consent/variables.d.ts +1 -0
  16. package/lib/components/Consent/variables.js +31 -0
  17. package/lib/components/Dropdown/Dropdown.js +3 -1
  18. package/lib/components/Footer/Footer.js +4 -1
  19. package/lib/components/JsonViewer/Helpers.d.ts +10 -2
  20. package/lib/components/JsonViewer/Helpers.js +29 -33
  21. package/lib/components/JsonViewer/JsonViewer.js +15 -18
  22. package/lib/components/Markdown/Markdown.js +3 -2
  23. package/lib/components/Markdown/styles/base-table.js +14 -1
  24. package/lib/components/Markdown/variables.js +1 -0
  25. package/lib/components/Menu/Menu.js +4 -0
  26. package/lib/components/Menu/MenuSelect.d.ts +7 -0
  27. package/lib/components/Menu/MenuSelect.js +129 -0
  28. package/lib/components/Menu/variables.js +43 -4
  29. package/lib/components/Navbar/Navbar.js +44 -5
  30. package/lib/components/Navbar/variables.js +3 -0
  31. package/lib/components/NavigationTabs/NavigationTab.d.ts +7 -0
  32. package/lib/components/NavigationTabs/NavigationTab.js +93 -0
  33. package/lib/components/NavigationTabs/NavigationTabContent.d.ts +7 -0
  34. package/lib/components/NavigationTabs/NavigationTabContent.js +50 -0
  35. package/lib/components/NavigationTabs/NavigationTabMenu.d.ts +7 -0
  36. package/lib/components/NavigationTabs/NavigationTabMenu.js +18 -0
  37. package/lib/components/NavigationTabs/NavigationTabs.d.ts +7 -0
  38. package/lib/components/NavigationTabs/NavigationTabs.js +97 -0
  39. package/lib/components/NavigationTabs/variables.d.ts +1 -0
  40. package/lib/components/NavigationTabs/variables.js +37 -0
  41. package/lib/components/Product/utils.js +1 -1
  42. package/lib/components/Search/SearchAiMessage.js +1 -1
  43. package/lib/components/Search/SearchAiResponse.js +1 -0
  44. package/lib/components/Search/SearchTrigger.js +8 -2
  45. package/lib/components/Sidebar/Sidebar.js +2 -2
  46. package/lib/components/TableOfContent/TableOfContent.js +2 -2
  47. package/lib/core/constants/common.d.ts +1 -0
  48. package/lib/core/constants/common.js +1 -0
  49. package/lib/core/constants/consent.d.ts +2 -0
  50. package/lib/core/constants/consent.js +30 -0
  51. package/lib/core/constants/index.d.ts +1 -0
  52. package/lib/core/constants/index.js +1 -0
  53. package/lib/core/hooks/index.d.ts +3 -0
  54. package/lib/core/hooks/index.js +3 -0
  55. package/lib/core/hooks/menu/use-mobile-menu-items.js +75 -51
  56. package/lib/core/hooks/menu/use-mobile-menu-levels.js +12 -7
  57. package/lib/core/hooks/use-consent-dialog.d.ts +12 -0
  58. package/lib/core/hooks/use-consent-dialog.js +36 -0
  59. package/lib/core/hooks/use-consent-telemetry.d.ts +1 -0
  60. package/lib/core/hooks/use-consent-telemetry.js +42 -0
  61. package/lib/core/hooks/use-scroll-item-into-view.d.ts +2 -0
  62. package/lib/core/hooks/use-scroll-item-into-view.js +16 -0
  63. package/lib/core/hooks/use-theme-hooks.js +16 -0
  64. package/lib/core/styles/global.js +54 -42
  65. package/lib/core/types/consent.d.ts +54 -0
  66. package/lib/core/types/consent.js +3 -0
  67. package/lib/core/types/hooks.d.ts +7 -0
  68. package/lib/core/types/index.d.ts +2 -0
  69. package/lib/core/types/l10n.d.ts +1 -1
  70. package/lib/core/types/navigation-tabs.d.ts +49 -0
  71. package/lib/core/types/navigation-tabs.js +3 -0
  72. package/lib/core/utils/consent-settings-link-placement.d.ts +3 -0
  73. package/lib/core/utils/consent-settings-link-placement.js +15 -0
  74. package/lib/core/utils/index.d.ts +2 -0
  75. package/lib/core/utils/index.js +2 -0
  76. package/lib/core/utils/mcp.js +1 -1
  77. package/lib/core/utils/menu.d.ts +4 -0
  78. package/lib/core/utils/menu.js +48 -8
  79. package/lib/core/utils/navigation-tabs.d.ts +8 -0
  80. package/lib/core/utils/navigation-tabs.js +71 -0
  81. package/lib/core/utils/urls.js +3 -1
  82. package/lib/icons/ExpandCollapseIcon/ExpandCollapseIcon.d.ts +29 -0
  83. package/lib/icons/ExpandCollapseIcon/ExpandCollapseIcon.js +81 -0
  84. package/lib/index.d.ts +6 -0
  85. package/lib/index.js +8 -1
  86. package/lib/layouts/PageLayout.js +1 -1
  87. package/lib/layouts/RootLayout.js +2 -2
  88. package/lib/markdoc/components/CodeWalkthrough/CodeFilters.js +1 -1
  89. package/lib/markdoc/components/CodeWalkthrough/CodePanel.js +3 -3
  90. package/lib/markdoc/components/CodeWalkthrough/CodeWalkthrough.js +1 -1
  91. package/package.json +3 -3
  92. package/src/components/Catalog/Catalog.tsx +1 -1
  93. package/src/components/Catalog/CatalogEntity/CatalogEntityHistory/CatalogEntityHistorySidebar.tsx +2 -2
  94. package/src/components/Catalog/CatalogEntity/CatalogEntitySchema.tsx +1 -1
  95. package/src/components/Catalog/CatalogMobileFiltersPanel/CatalogMobileFiltersPanel.tsx +1 -1
  96. package/src/components/Catalog/CatalogMobileFiltersPanel/CatalogMobileFiltersPanelOverlay.tsx +2 -2
  97. package/src/components/CodeBlock/CodeBlockControls.tsx +4 -7
  98. package/src/components/Consent/Consent.tsx +18 -0
  99. package/src/components/Consent/ConsentBanner.tsx +110 -0
  100. package/src/components/Consent/ConsentDialog.tsx +210 -0
  101. package/src/components/Consent/ConsentSettingsLink.tsx +44 -0
  102. package/src/components/Consent/variables.ts +28 -0
  103. package/src/components/Dropdown/Dropdown.tsx +4 -1
  104. package/src/components/Footer/Footer.tsx +2 -0
  105. package/src/components/JsonViewer/Helpers.tsx +46 -51
  106. package/src/components/JsonViewer/JsonViewer.tsx +23 -25
  107. package/src/components/Markdown/Markdown.tsx +3 -2
  108. package/src/components/Markdown/styles/base-table.ts +14 -1
  109. package/src/components/Markdown/variables.ts +1 -0
  110. package/src/components/Menu/Menu.tsx +5 -0
  111. package/src/components/Menu/MenuSelect.tsx +198 -0
  112. package/src/components/Menu/variables.ts +43 -4
  113. package/src/components/Navbar/Navbar.tsx +22 -5
  114. package/src/components/Navbar/variables.ts +3 -0
  115. package/src/components/NavigationTabs/NavigationTab.tsx +148 -0
  116. package/src/components/NavigationTabs/NavigationTabContent.tsx +66 -0
  117. package/src/components/NavigationTabs/NavigationTabMenu.tsx +40 -0
  118. package/src/components/NavigationTabs/NavigationTabs.tsx +83 -0
  119. package/src/components/NavigationTabs/variables.ts +34 -0
  120. package/src/components/Product/utils.ts +1 -1
  121. package/src/components/Search/SearchAiMessage.tsx +1 -1
  122. package/src/components/Search/SearchAiResponse.tsx +1 -0
  123. package/src/components/Search/SearchTrigger.tsx +11 -2
  124. package/src/components/Sidebar/Sidebar.tsx +2 -2
  125. package/src/components/TableOfContent/TableOfContent.tsx +2 -2
  126. package/src/core/constants/common.ts +1 -0
  127. package/src/core/constants/consent.ts +29 -0
  128. package/src/core/constants/index.ts +1 -0
  129. package/src/core/hooks/__mocks__/use-theme-hooks.ts +17 -0
  130. package/src/core/hooks/index.ts +3 -0
  131. package/src/core/hooks/menu/use-mobile-menu-items.ts +91 -56
  132. package/src/core/hooks/menu/use-mobile-menu-levels.ts +14 -6
  133. package/src/core/hooks/use-consent-dialog.ts +51 -0
  134. package/src/core/hooks/use-consent-telemetry.ts +56 -0
  135. package/src/core/hooks/use-scroll-item-into-view.ts +18 -0
  136. package/src/core/hooks/use-theme-hooks.ts +16 -0
  137. package/src/core/styles/global.ts +14 -2
  138. package/src/core/types/consent.ts +51 -0
  139. package/src/core/types/hooks.ts +7 -0
  140. package/src/core/types/index.ts +2 -0
  141. package/src/core/types/l10n.ts +25 -1
  142. package/src/core/types/navigation-tabs.ts +54 -0
  143. package/src/core/utils/consent-settings-link-placement.ts +18 -0
  144. package/src/core/utils/index.ts +2 -0
  145. package/src/core/utils/mcp.ts +1 -1
  146. package/src/core/utils/menu.ts +54 -9
  147. package/src/core/utils/navigation-tabs.ts +99 -0
  148. package/src/core/utils/urls.ts +3 -1
  149. package/src/icons/ExpandCollapseIcon/ExpandCollapseIcon.tsx +56 -0
  150. package/src/index.ts +7 -0
  151. package/src/layouts/PageLayout.tsx +1 -1
  152. package/src/layouts/RootLayout.tsx +2 -2
  153. package/src/markdoc/components/CodeWalkthrough/CodeFilters.tsx +1 -1
  154. package/src/markdoc/components/CodeWalkthrough/CodePanel.tsx +3 -3
  155. package/src/markdoc/components/CodeWalkthrough/CodeWalkthrough.tsx +1 -1
@@ -71,7 +71,7 @@ export const Markdown = styled.main
71
71
  ${markdownLinksCss}
72
72
 
73
73
  a[name], [id] {
74
- scroll-margin-top: calc(var(--navbar-height) + var(--banner-height));
74
+ scroll-margin-top: var(--navbar-offset);
75
75
  }
76
76
 
77
77
  img,
@@ -84,7 +84,7 @@ export const Markdown = styled.main
84
84
  font-family: var(--heading-font-family);
85
85
  position: relative;
86
86
 
87
- scroll-margin-top: calc(var(--navbar-height) + var(--banner-height));
87
+ scroll-margin-top: var(--navbar-offset);
88
88
  ${headingAnchorCss(ANCHOR_CLASS)};
89
89
 
90
90
  h1,
@@ -295,6 +295,7 @@ export const Markdown = styled.main
295
295
  font-size: inherit;
296
296
  font-family: inherit;
297
297
  border: none;
298
+ padding: 0;
298
299
  }
299
300
  }
300
301
 
@@ -22,7 +22,7 @@ export const markdownBaseTableCss = css`
22
22
  color: var(--md-table-cell-text-color);
23
23
  font-size: var(--md-table-font-size);
24
24
  line-height: var(--md-table-line-height);
25
- vertical-align: top;
25
+ vertical-align: var(--md-table-cell-vertical-align);
26
26
  }
27
27
 
28
28
  thead td,
@@ -71,5 +71,18 @@ export const markdownBaseTableCss = css`
71
71
  th[align='right'] {
72
72
  text-align: right;
73
73
  }
74
+
75
+ th[valign='top'],
76
+ td[valign='top'] {
77
+ vertical-align: top;
78
+ }
79
+ th[valign='middle'],
80
+ td[valign='middle'] {
81
+ vertical-align: middle;
82
+ }
83
+ th[valign='bottom'],
84
+ td[valign='bottom'] {
85
+ vertical-align: bottom;
86
+ }
74
87
  }
75
88
  `;
@@ -51,6 +51,7 @@ export const markdown = css`
51
51
  --md-table-cell-text-color: var(--text-color-secondary); // @presenter Color
52
52
  --md-table-cell-padding: var(--spacing-xs) var(--spacing-sm); // @presenter Spacing
53
53
  --md-table-footer-cell-padding: var(--spacing-xxs) var(--spacing-sm); // @presenter Spacing
54
+ --md-table-cell-vertical-align: top;
54
55
 
55
56
  --md-table-head-bg-color: var(--md-table-bg-color); // @presenter Color
56
57
  --md-table-head-text-color: var(--text-color-primary); // @presenter Color
@@ -5,6 +5,7 @@ import type { JSX } from 'react';
5
5
  import type { DrilldownMenuItemDetails, ItemState } from '@redocly/theme/core/types';
6
6
 
7
7
  import { MenuItem } from '@redocly/theme/components/Menu/MenuItem';
8
+ import { MenuSelect } from '@redocly/theme/components/Menu/MenuSelect';
8
9
  import { MenuItemType } from '@redocly/theme/core/constants';
9
10
  import { getMenuItemType } from '@redocly/theme/core/utils';
10
11
 
@@ -28,6 +29,10 @@ export function Menu({
28
29
  return (
29
30
  <MenuWrapper className={className} data-component-name="Menu/Menu">
30
31
  {items.map((item, index) => {
32
+ if (getMenuItemType(item) === MenuItemType.Select) {
33
+ return <MenuSelect item={item} key={`${item.label}-${index}`} />;
34
+ }
35
+
31
36
  return (
32
37
  <MenuItem
33
38
  item={item}
@@ -0,0 +1,198 @@
1
+ import React from 'react';
2
+ import styled from 'styled-components';
3
+
4
+ import type { JSX } from 'react';
5
+ import type { ItemState } from '@redocly/theme/core/types';
6
+
7
+ import { getBaseDataAttributes } from '@redocly/theme/core/utils';
8
+ import { useThemeHooks } from '@redocly/theme/core/hooks';
9
+ import { Dropdown } from '@redocly/theme/components/Dropdown/Dropdown';
10
+ import { DropdownMenu } from '@redocly/theme/components/Dropdown/DropdownMenu';
11
+ import { DropdownMenuItem } from '@redocly/theme/components/Dropdown/DropdownMenuItem';
12
+ import { GenericIcon } from '@redocly/theme/icons/GenericIcon/GenericIcon';
13
+ import { LaunchIcon } from '@redocly/theme/icons/LaunchIcon/LaunchIcon';
14
+ import { ChevronDownIcon } from '@redocly/theme/icons/ChevronDownIcon/ChevronDownIcon';
15
+ import { CheckmarkIcon } from '@redocly/theme/icons/CheckmarkIcon/CheckmarkIcon';
16
+
17
+ export type MenuSelectProps = {
18
+ item: ItemState;
19
+ className?: string;
20
+ };
21
+
22
+ export function MenuSelect({ item, className }: MenuSelectProps): JSX.Element {
23
+ const { useTranslate, useTelemetry } = useThemeHooks();
24
+ const { translate } = useTranslate();
25
+ const telemetry = useTelemetry();
26
+
27
+ return (
28
+ <MenuSelectWrapper data-component-name="Menu/MenuSelect" className={className} role="listitem">
29
+ <MenuSelectDropdown
30
+ alignment="start"
31
+ trigger={
32
+ <MenuSelectTrigger type="button">
33
+ <MenuSelectLabel>{translate(item.labelTranslationKey, item.label)}</MenuSelectLabel>
34
+ <MenuSelectChevron
35
+ size="var(--menu-select-icon-size)"
36
+ color="--menu-select-icon-color"
37
+ />
38
+ </MenuSelectTrigger>
39
+ }
40
+ >
41
+ <MenuSelectMenu>
42
+ {item.items.map((option, index) => {
43
+ const key = `${option.link || option.label}-${index}`;
44
+ const checkmark = option.active ? (
45
+ <MenuSelectCheckmark size="var(--menu-select-option-checkmark-size)" />
46
+ ) : null;
47
+ const content = (
48
+ <MenuSelectOptionContent $active={option.active}>
49
+ {option.icon || option.srcSet ? (
50
+ <MenuSelectOptionIcon icon={option.icon} srcSet={option.srcSet} />
51
+ ) : null}
52
+ {translate(option.labelTranslationKey, option.label)}
53
+ {option.external ? (
54
+ <LaunchIcon size="var(--menu-select-option-external-icon-size)" />
55
+ ) : null}
56
+ </MenuSelectOptionContent>
57
+ );
58
+
59
+ if (!option.link) {
60
+ return (
61
+ <MenuSelectOption
62
+ key={key}
63
+ active={option.active}
64
+ prefix={checkmark}
65
+ dataAttributes={{ 'aria-disabled': 'true' }}
66
+ >
67
+ {content}
68
+ </MenuSelectOption>
69
+ );
70
+ }
71
+
72
+ return (
73
+ <MenuSelectOption
74
+ key={key}
75
+ to={option.link}
76
+ external={option.external}
77
+ target={option.target === '_blank' ? '_blank' : undefined}
78
+ active={option.active}
79
+ aria-current={option.active ? 'page' : undefined}
80
+ prefix={checkmark}
81
+ onAction={() =>
82
+ telemetry.sendSidebarItemClickedMessage([
83
+ {
84
+ ...getBaseDataAttributes('sidebarItem', 'sidebar'),
85
+ label: option.label,
86
+ type: 'link',
87
+ },
88
+ ])
89
+ }
90
+ >
91
+ {content}
92
+ </MenuSelectOption>
93
+ );
94
+ })}
95
+ </MenuSelectMenu>
96
+ </MenuSelectDropdown>
97
+ </MenuSelectWrapper>
98
+ );
99
+ }
100
+
101
+ const MenuSelectWrapper = styled.div`
102
+ margin: var(--menu-select-margin);
103
+ `;
104
+
105
+ const MenuSelectDropdown = styled(Dropdown)`
106
+ --dropdown-menu-padding-top: var(--menu-select-menu-offset);
107
+ --dropdown-menu-padding: var(--menu-select-menu-padding);
108
+ --dropdown-menu-border-radius: var(--menu-select-menu-border-radius);
109
+ --dropdown-menu-item-gap: var(--menu-select-option-gap);
110
+ --dropdown-menu-item-border-radius: var(--menu-select-option-border-radius);
111
+ --dropdown-menu-item-bg-color-active: var(--menu-select-option-bg-color-active);
112
+ --dropdown-menu-item-color-active: var(--menu-select-option-text-color-active);
113
+
114
+ width: 100%;
115
+
116
+ > * {
117
+ min-width: 100%;
118
+ }
119
+ `;
120
+
121
+ const MenuSelectChevron = styled(ChevronDownIcon)`
122
+ transition: var(--menu-select-transition);
123
+ `;
124
+
125
+ const MenuSelectTrigger = styled.button`
126
+ display: flex;
127
+ align-items: center;
128
+ gap: var(--menu-select-gap);
129
+ box-sizing: border-box;
130
+ width: 100%;
131
+ height: var(--menu-select-height);
132
+ padding: 0 var(--menu-select-padding-horizontal);
133
+ border: var(--menu-select-border);
134
+ border-radius: var(--menu-select-border-radius);
135
+ background-color: var(--menu-select-bg-color);
136
+ color: var(--menu-select-text-color);
137
+ font-family: inherit;
138
+ font-size: var(--menu-select-font-size);
139
+ line-height: var(--menu-select-line-height);
140
+ text-align: left;
141
+ cursor: pointer;
142
+
143
+ &:focus-visible {
144
+ outline: 1px solid var(--menu-select-border-color-focused);
145
+ }
146
+
147
+ &[aria-expanded='true'] ${MenuSelectChevron} {
148
+ transform: rotate(180deg);
149
+ }
150
+ `;
151
+
152
+ const MenuSelectLabel = styled.span`
153
+ flex: 1;
154
+ min-width: 0;
155
+ overflow: hidden;
156
+ text-overflow: ellipsis;
157
+ white-space: nowrap;
158
+ `;
159
+
160
+ const MenuSelectMenu = styled(DropdownMenu)`
161
+ box-sizing: border-box;
162
+ width: 100%;
163
+ max-width: 100%;
164
+ `;
165
+
166
+ const MenuSelectOption = styled(DropdownMenuItem)`
167
+ position: relative;
168
+ box-sizing: border-box;
169
+ padding: var(--menu-select-option-padding);
170
+
171
+ &[aria-disabled='true'] {
172
+ cursor: default;
173
+ pointer-events: none;
174
+ }
175
+ `;
176
+
177
+ const MenuSelectCheckmark = styled(CheckmarkIcon)`
178
+ position: absolute;
179
+ top: 50%;
180
+ left: var(--menu-select-option-checkmark-offset);
181
+ transform: translateY(-50%);
182
+ `;
183
+
184
+ const MenuSelectOptionContent = styled.span<{ $active: boolean }>`
185
+ display: inline-flex;
186
+ align-items: center;
187
+ gap: var(--menu-select-option-content-gap);
188
+ font-weight: ${({ $active }) =>
189
+ $active
190
+ ? 'var(--menu-select-option-font-weight-active)'
191
+ : 'var(--menu-select-option-font-weight)'};
192
+ `;
193
+
194
+ const MenuSelectOptionIcon = styled(GenericIcon)`
195
+ --icon-width: var(--menu-select-option-icon-size);
196
+ --icon-height: var(--menu-select-option-icon-size);
197
+ flex-shrink: 0;
198
+ `;
@@ -102,6 +102,45 @@ export const menu = css`
102
102
  --menu-back-button-gap: var(--spacing-xs);
103
103
  --menu-back-button-icon: none;
104
104
 
105
+ /**
106
+ * @tokens Menu select
107
+ */
108
+ --menu-select-margin-top: 0;
109
+ --menu-select-margin-bottom: var(--spacing-xs);
110
+ --menu-select-margin-horizontal: var(--menu-item-label-margin-horizontal);
111
+ --menu-select-margin: var(--menu-select-margin-top) var(--menu-select-margin-horizontal) var(--menu-select-margin-bottom); // @presenter Spacing
112
+ --menu-select-height: 32px; // @presenter Spacing
113
+ --menu-select-padding-horizontal: calc(var(--spacing-sm) - var(--border-width)); // @presenter Spacing
114
+ --menu-select-gap: var(--spacing-xxs); // @presenter Spacing
115
+ --menu-select-font-size: var(--font-size-base); // @presenter FontSize
116
+ --menu-select-line-height: var(--line-height-base); // @presenter LineHeight
117
+ --menu-select-text-color: var(--input-content-color-active); // @presenter Color
118
+ --menu-select-bg-color: var(--input-bg-color); // @presenter Color
119
+ --menu-select-border: var(--input-border); // @presenter Border
120
+ --menu-select-border-color-focused: var(--input-border-color-focused); // @presenter Color
121
+ --menu-select-border-radius: var(--input-border-radius); // @presenter BorderRadius
122
+ --menu-select-icon-size: 16px;
123
+ --menu-select-icon-color: var(--icon-color-secondary); // @presenter Color
124
+ --menu-select-transition: transform 150ms ease-in-out;
125
+ --menu-select-menu-offset: var(--spacing-xs); // @presenter Spacing
126
+ --menu-select-menu-padding: calc(var(--dropdown-menu-padding) - var(--border-width)); // @presenter Spacing
127
+ --menu-select-menu-border-radius: var(--border-radius-lg); // @presenter BorderRadius
128
+ --menu-select-option-gap: calc(var(--spacing-unit) / 2); // @presenter Spacing
129
+ --menu-select-option-content-gap: var(--spacing-xs); // @presenter Spacing
130
+ --menu-select-option-padding-vertical: var(--dropdown-menu-item-padding-vertical);
131
+ --menu-select-option-padding-left: var(--spacing-md);
132
+ --menu-select-option-padding-right: var(--spacing-sm);
133
+ --menu-select-option-padding: var(--menu-select-option-padding-vertical) var(--menu-select-option-padding-right) var(--menu-select-option-padding-vertical) var(--menu-select-option-padding-left); // @presenter Spacing
134
+ --menu-select-option-border-radius: var(--border-radius-md); // @presenter BorderRadius
135
+ --menu-select-option-font-weight: var(--font-weight-regular); // @presenter FontWeight
136
+ --menu-select-option-font-weight-active: var(--font-weight-medium); // @presenter FontWeight
137
+ --menu-select-option-text-color-active: var(--text-color-primary); // @presenter Color
138
+ --menu-select-option-bg-color-active: transparent; // @presenter Color
139
+ --menu-select-option-icon-size: var(--menu-item-icon-size);
140
+ --menu-select-option-external-icon-size: var(--menu-item-external-icon-size);
141
+ --menu-select-option-checkmark-size: 14px;
142
+ --menu-select-option-checkmark-offset: calc((var(--menu-select-option-padding-left) - var(--menu-select-option-checkmark-size)) / 2);
143
+
105
144
  /**
106
145
  * @tokens Menu items
107
146
  */
@@ -133,18 +172,18 @@ export const mobileMenu = css`
133
172
  * @tokens Mobile Menu
134
173
  * */
135
174
  /* Fallback for older browsers. dvh accounts for dynamic UI elements like mobile address bars */
136
- --menu-mobile-height: calc(100vh - var(--navbar-height) - var(--banner-height));
137
- --menu-mobile-height: calc(100dvh - var(--navbar-height) - var(--banner-height));
175
+ --menu-mobile-height: calc(100vh - var(--navbar-offset));
176
+ --menu-mobile-height: calc(100dvh - var(--navbar-offset));
138
177
  --menu-mobile-width: 100%;
139
178
  --menu-mobile-z-index: var(--z-index-raised);
140
179
  --menu-mobile-left: 0;
141
- --menu-mobile-top: calc(var(--navbar-height) + var(--banner-height));
180
+ --menu-mobile-top: var(--navbar-offset);
142
181
  --menu-mobile-transition: 0.5s;
143
182
  --menu-mobile-bg: var(--bg-color); // @presenter Color
144
183
  --menu-mobile-margin: var(--menu-mobile-items-margin-top) var(--menu-mobile-margin-horizontal) 0 var(--menu-mobile-margin-horizontal);
145
184
  --menu-mobile-line-height: var(--line-height-base); // @presenter LineHeight
146
185
  --menu-mobile-padding-top: var(--spacing-sm); // @presenter Spacing
147
- --menu-mobile-margin-horizontal: var(--spacing-xs); // @presenter Spacing
186
+ --menu-mobile-margin-horizontal: 0; // @presenter Spacing
148
187
  --menu-mobile-items-margin-top: var(--spacing-xxs); // @presenter Spacing
149
188
 
150
189
  --menu-mobile-profile-bg-color: var(--bg-color); // @presenter Color
@@ -1,13 +1,18 @@
1
- import React from 'react';
1
+ import React, { useMemo } from 'react';
2
2
  import styled from 'styled-components';
3
3
 
4
4
  import type { JSX } from 'react';
5
5
  import type { ResolvedConfigLinks } from '@redocly/config';
6
6
 
7
- import { breakpoints, getBaseDataAttributes } from '@redocly/theme/core/utils';
7
+ import {
8
+ breakpoints,
9
+ getBaseDataAttributes,
10
+ toNavigationTabsConfig,
11
+ } from '@redocly/theme/core/utils';
8
12
  import { useThemeHooks, useThemeConfig, useMobileMenu } from '@redocly/theme/core/hooks';
9
13
  import { NavbarLogo } from '@redocly/theme/components/Navbar/NavbarLogo';
10
14
  import { NavbarMenu } from '@redocly/theme/components/Navbar/NavbarMenu';
15
+ import { NavigationTabs } from '@redocly/theme/components/NavigationTabs/NavigationTabs';
11
16
  import { MenuMobile } from '@redocly/theme/components/Menu/MenuMobile';
12
17
  import { ColorModeSwitcher } from '@redocly/theme/components/ColorModeSwitcher/ColorModeSwitcher';
13
18
  import { Search } from '@redocly/theme/components/Search/Search';
@@ -26,9 +31,15 @@ export type NavbarProps = {
26
31
  export function Navbar({ className }: NavbarProps): JSX.Element | null {
27
32
  const { isOpen, closeMobileMenu, openMobileMenu } = useMobileMenu(false);
28
33
  const themeConfig = useThemeConfig();
29
- const { useL10n, useTelemetry } = useThemeHooks();
34
+ const { useL10n, useTelemetry, useTranslate, useNavigationTabs } = useThemeHooks();
30
35
  const { changeLanguage } = useL10n();
36
+ const { translate } = useTranslate();
31
37
  const telemetry = useTelemetry();
38
+ const navigationTabs = useNavigationTabs();
39
+ const navigationTabsConfig = useMemo(
40
+ () => toNavigationTabsConfig({ state: navigationTabs }),
41
+ [navigationTabs],
42
+ );
32
43
 
33
44
  const menu = themeConfig.navbar?.items;
34
45
 
@@ -43,7 +54,11 @@ export function Navbar({ className }: NavbarProps): JSX.Element | null {
43
54
  const hideUserMenu = userMenuSettings?.hide;
44
55
 
45
56
  return (
46
- <NavbarWrapper data-component-name="Navbar/Navbar" className={className}>
57
+ <NavbarWrapper
58
+ data-component-name="Navbar/Navbar"
59
+ className={className}
60
+ aria-label={translate('navbar.label', 'Main navigation')}
61
+ >
47
62
  <Banner />
48
63
  {isOpen && <MenuMobile hideUserProfile={!!hideUserMenu} />}
49
64
  <NavbarRow>
@@ -76,13 +91,14 @@ export function Navbar({ className }: NavbarProps): JSX.Element | null {
76
91
  />
77
92
  {hideUserMenu ? null : <UserMenu />}
78
93
  </NavbarRow>
94
+ {navigationTabsConfig && <NavigationTabs config={navigationTabsConfig} />}
79
95
  </NavbarWrapper>
80
96
  );
81
97
  }
82
98
 
83
99
  const NavbarWrapper = styled.nav`
84
100
  --text-color: var(--navbar-text-color);
85
- height: calc(var(--navbar-height) + var(--banner-height));
101
+ height: var(--navbar-offset, calc(var(--navbar-height) + var(--banner-height)));
86
102
  transition: height 0.4s ease-out;
87
103
 
88
104
  position: sticky;
@@ -123,6 +139,7 @@ const NavbarRow = styled.div`
123
139
  box-sizing: border-box;
124
140
  margin-top: var(--banner-height);
125
141
  transition: margin-top 0.5s ease-out;
142
+ border-bottom-color: var(--navbar-row-bottom-border-color, var(--navbar-border-color));
126
143
 
127
144
  @media screen and (min-width: ${breakpoints.max}) {
128
145
  max-width: var(--container-max-width);
@@ -4,9 +4,12 @@ export const navbar = css`
4
4
  --navbar-bg-color: var(--bg-color); // @presenter Color
5
5
  --navbar-border-color: var(--border-color-secondary);
6
6
  --navbar-border: 1px solid var(--navbar-border-color);
7
+ --navbar-row-bottom-border-color: var(--navbar-border-color);
7
8
 
8
9
  --navbar-text-color: var(--text-color-secondary); // @presenter Color
9
10
  --navbar-height: 64px; // @presenter Spacing
11
+ --navbar-stack-height: var(--navbar-height);
12
+ --navbar-offset: calc(var(--navbar-stack-height) + var(--banner-height));
10
13
  --navbar-font-size: var(--font-size-base);
11
14
  --navbar-padding: 0 var(--spacing-xl); // @presenter Spacing
12
15
  --navbar-font-family: var(--font-family-base);
@@ -0,0 +1,148 @@
1
+ import React from 'react';
2
+ import styled from 'styled-components';
3
+
4
+ import type { JSX } from 'react';
5
+ import type { LinkProps, NavigationTabRowItem } from '@redocly/theme/core/types';
6
+
7
+ import { getNavbarItemClickedPayload } from '@redocly/theme/core/utils';
8
+ import { useThemeHooks } from '@redocly/theme/core/hooks';
9
+ import { Link } from '@redocly/theme/components/Link/Link';
10
+ import { Dropdown } from '@redocly/theme/components/Dropdown/Dropdown';
11
+ import { NavigationTabMenu } from '@redocly/theme/components/NavigationTabs/NavigationTabMenu';
12
+ import { NavigationTabContent } from '@redocly/theme/components/NavigationTabs/NavigationTabContent';
13
+ import { ChevronDownIcon } from '@redocly/theme/icons/ChevronDownIcon/ChevronDownIcon';
14
+
15
+ export type NavigationTabProps = {
16
+ tab: NavigationTabRowItem;
17
+ className?: string;
18
+ };
19
+
20
+ export function NavigationTab({ tab, className }: NavigationTabProps): JSX.Element | null {
21
+ const { useTelemetry } = useThemeHooks();
22
+ const telemetry = useTelemetry();
23
+
24
+ const active = !!tab.active;
25
+ const menuItems = tab.type === 'group' ? tab.items : [];
26
+ const isGroup = tab.type === 'group' && menuItems.length > 0;
27
+ const linkItem = tab.type === 'link' ? tab : undefined;
28
+
29
+ if (!linkItem?.link && !isGroup) {
30
+ return null;
31
+ }
32
+
33
+ const content = (
34
+ <>
35
+ <NavigationTabContent item={tab} />
36
+ {isGroup ? <NavigationTabChevron size="10px" /> : null}
37
+ </>
38
+ );
39
+
40
+ if (isGroup) {
41
+ return (
42
+ <NavigationTabWrapper
43
+ data-component-name="NavigationTabs/NavigationTab"
44
+ className={className}
45
+ >
46
+ <NavigationTabDropdown
47
+ triggerEvent="hover"
48
+ portalled
49
+ trigger={
50
+ <NavigationTabTrigger
51
+ $active={active}
52
+ aria-current={active ? 'true' : undefined}
53
+ onClick={() =>
54
+ telemetry.sendNavbarMenuItemClickedMessage(getNavbarItemClickedPayload(tab.type))
55
+ }
56
+ >
57
+ {content}
58
+ </NavigationTabTrigger>
59
+ }
60
+ >
61
+ <NavigationTabMenu items={menuItems} />
62
+ </NavigationTabDropdown>
63
+ </NavigationTabWrapper>
64
+ );
65
+ }
66
+
67
+ return (
68
+ <NavigationTabWrapper data-component-name="NavigationTabs/NavigationTab" className={className}>
69
+ <NavigationTabLink
70
+ as={Link}
71
+ to={linkItem?.link}
72
+ $active={active}
73
+ external={linkItem?.external}
74
+ target={linkItem?.target}
75
+ aria-current={active ? 'page' : undefined}
76
+ onClick={() =>
77
+ telemetry.sendNavbarMenuItemClickedMessage(getNavbarItemClickedPayload(tab.type))
78
+ }
79
+ >
80
+ {content}
81
+ </NavigationTabLink>
82
+ </NavigationTabWrapper>
83
+ );
84
+ }
85
+
86
+ const NavigationTabChevron = styled(ChevronDownIcon)`
87
+ flex-shrink: 0;
88
+ `;
89
+
90
+ const NavigationTabWrapper = styled.li`
91
+ list-style: none;
92
+ display: inline-flex;
93
+ align-items: stretch;
94
+ flex-shrink: 0;
95
+ `;
96
+
97
+ const NavigationTabDropdown = styled(Dropdown)`
98
+ display: inline-flex;
99
+ align-items: stretch;
100
+ height: 100%;
101
+ `;
102
+
103
+ const NavigationTabLink = styled.div<LinkProps & { $active: boolean }>`
104
+ display: inline-flex;
105
+ align-items: center;
106
+ gap: var(--navigation-tabs-item-content-gap);
107
+ box-sizing: border-box;
108
+ height: 100%;
109
+ padding: var(--navigation-tabs-item-padding);
110
+ font-size: var(--navigation-tabs-item-font-size);
111
+ line-height: var(--navigation-tabs-item-line-height);
112
+ white-space: nowrap;
113
+ text-decoration: none;
114
+ transition: border-color var(--navigation-tabs-item-transition);
115
+
116
+ font-weight: ${({ $active }) =>
117
+ $active
118
+ ? 'var(--navigation-tabs-item-font-weight-active)'
119
+ : 'var(--navigation-tabs-item-font-weight)'};
120
+ color: ${({ $active }) =>
121
+ $active
122
+ ? 'var(--navigation-tabs-item-text-color-active)'
123
+ : 'var(--navigation-tabs-item-text-color)'};
124
+ border-bottom: ${({ $active }) =>
125
+ $active
126
+ ? 'var(--navigation-tabs-item-bottom-border-active)'
127
+ : 'var(--navigation-tabs-item-bottom-border)'};
128
+
129
+ &:hover {
130
+ color: var(--navigation-tabs-item-text-color-hover);
131
+ border-bottom: ${({ $active }) => !$active && 'var(--navigation-tabs-item-bottom-border-hover)'};
132
+ }
133
+
134
+ &:focus-visible {
135
+ outline: 1px solid var(--navigation-tabs-item-border-color-focused);
136
+ outline-offset: 2px;
137
+ border-radius: var(--navigation-tabs-item-border-radius);
138
+ }
139
+ `;
140
+
141
+ const NavigationTabTrigger = styled(NavigationTabLink).attrs({ as: 'button', type: 'button' })`
142
+ background: none;
143
+ border-top: none;
144
+ border-left: none;
145
+ border-right: none;
146
+ font-family: inherit;
147
+ cursor: pointer;
148
+ `;
@@ -0,0 +1,66 @@
1
+ import React from 'react';
2
+ import styled from 'styled-components';
3
+
4
+ import type { JSX } from 'react';
5
+ import type { NavigationTabRowItem } from '@redocly/theme/core/types';
6
+
7
+ import { useThemeHooks } from '@redocly/theme/core/hooks';
8
+ import { GenericIcon } from '@redocly/theme/icons/GenericIcon/GenericIcon';
9
+ import { LaunchIcon } from '@redocly/theme/icons/LaunchIcon/LaunchIcon';
10
+
11
+ export type NavigationTabContentProps = {
12
+ item: NavigationTabRowItem;
13
+ className?: string;
14
+ };
15
+
16
+ export function NavigationTabContent({ item, className }: NavigationTabContentProps): JSX.Element {
17
+ const { useTranslate } = useThemeHooks();
18
+ const { translate } = useTranslate();
19
+ const label = translate(item.labelTranslationKey, item.label);
20
+
21
+ return (
22
+ <NavigationTabContentWrapper
23
+ data-component-name="NavigationTabs/NavigationTabContent"
24
+ className={className}
25
+ >
26
+ {(item.icon || item.srcSet) && <NavigationTabIcon icon={item.icon} srcSet={item.srcSet} />}
27
+ <NavigationTabLabel data-label={label} data-translation-key={item.labelTranslationKey}>
28
+ {label}
29
+ </NavigationTabLabel>
30
+ {'external' in item && item.external ? <NavigationTabExternalIcon size="10px" /> : null}
31
+ </NavigationTabContentWrapper>
32
+ );
33
+ }
34
+
35
+ const NavigationTabContentWrapper = styled.span`
36
+ display: inline-flex;
37
+ align-items: center;
38
+ gap: var(--navigation-tabs-item-content-gap);
39
+ `;
40
+
41
+ const NavigationTabIcon = styled(GenericIcon)`
42
+ --icon-width: var(--navigation-tabs-item-icon-size);
43
+ --icon-height: var(--navigation-tabs-item-icon-size);
44
+ flex-shrink: 0;
45
+ `;
46
+
47
+ const NavigationTabLabel = styled.span`
48
+ display: inline-flex;
49
+ flex-direction: column;
50
+ cursor: pointer;
51
+ vertical-align: middle;
52
+
53
+ &::after {
54
+ content: attr(data-label);
55
+ height: 0;
56
+ overflow: hidden;
57
+ visibility: hidden;
58
+ pointer-events: none;
59
+ font-weight: var(--navigation-tabs-item-label-font-weight-active);
60
+ }
61
+ `;
62
+
63
+ const NavigationTabExternalIcon = styled(LaunchIcon)`
64
+ margin-left: var(--navigation-tabs-item-external-icon-margin-left);
65
+ flex-shrink: 0;
66
+ `;