@redocly/theme 0.70.0-next.3 → 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.
- package/lib/components/Catalog/Catalog.js +1 -1
- package/lib/components/Catalog/CatalogEntity/CatalogEntityHistory/CatalogEntityHistorySidebar.js +2 -2
- package/lib/components/Catalog/CatalogEntity/CatalogEntitySchema.js +1 -1
- package/lib/components/Catalog/CatalogMobileFiltersPanel/CatalogMobileFiltersPanel.js +1 -1
- package/lib/components/Catalog/CatalogMobileFiltersPanel/CatalogMobileFiltersPanelOverlay.js +2 -2
- package/lib/components/Dropdown/Dropdown.js +3 -1
- package/lib/components/Markdown/Markdown.js +3 -2
- package/lib/components/Menu/Menu.js +4 -0
- package/lib/components/Menu/MenuSelect.d.ts +7 -0
- package/lib/components/Menu/MenuSelect.js +129 -0
- package/lib/components/Menu/variables.js +43 -4
- package/lib/components/Navbar/Navbar.js +44 -5
- package/lib/components/Navbar/variables.js +3 -0
- package/lib/components/NavigationTabs/NavigationTab.d.ts +7 -0
- package/lib/components/NavigationTabs/NavigationTab.js +93 -0
- package/lib/components/NavigationTabs/NavigationTabContent.d.ts +7 -0
- package/lib/components/NavigationTabs/NavigationTabContent.js +50 -0
- package/lib/components/NavigationTabs/NavigationTabMenu.d.ts +7 -0
- package/lib/components/NavigationTabs/NavigationTabMenu.js +18 -0
- package/lib/components/NavigationTabs/NavigationTabs.d.ts +7 -0
- package/lib/components/NavigationTabs/NavigationTabs.js +97 -0
- package/lib/components/NavigationTabs/variables.d.ts +1 -0
- package/lib/components/NavigationTabs/variables.js +37 -0
- package/lib/components/Search/SearchTrigger.js +8 -2
- package/lib/components/Sidebar/Sidebar.js +2 -2
- package/lib/components/TableOfContent/TableOfContent.js +2 -2
- package/lib/core/constants/common.d.ts +1 -0
- package/lib/core/constants/common.js +1 -0
- package/lib/core/hooks/index.d.ts +1 -0
- package/lib/core/hooks/index.js +1 -0
- package/lib/core/hooks/menu/use-mobile-menu-items.js +75 -51
- package/lib/core/hooks/menu/use-mobile-menu-levels.js +12 -7
- package/lib/core/hooks/use-scroll-item-into-view.d.ts +2 -0
- package/lib/core/hooks/use-scroll-item-into-view.js +16 -0
- package/lib/core/hooks/use-theme-hooks.js +2 -0
- package/lib/core/styles/global.js +12 -2
- package/lib/core/types/hooks.d.ts +5 -0
- package/lib/core/types/index.d.ts +1 -0
- package/lib/core/types/l10n.d.ts +1 -1
- package/lib/core/types/navigation-tabs.d.ts +49 -0
- package/lib/core/types/navigation-tabs.js +3 -0
- package/lib/core/utils/index.d.ts +1 -0
- package/lib/core/utils/index.js +1 -0
- package/lib/core/utils/menu.d.ts +4 -0
- package/lib/core/utils/menu.js +42 -3
- package/lib/core/utils/navigation-tabs.d.ts +8 -0
- package/lib/core/utils/navigation-tabs.js +71 -0
- package/lib/index.d.ts +5 -0
- package/lib/index.js +7 -1
- package/lib/layouts/PageLayout.js +1 -1
- package/lib/layouts/RootLayout.js +2 -2
- package/lib/markdoc/components/CodeWalkthrough/CodeFilters.js +1 -1
- package/lib/markdoc/components/CodeWalkthrough/CodePanel.js +3 -3
- package/lib/markdoc/components/CodeWalkthrough/CodeWalkthrough.js +1 -1
- package/package.json +3 -3
- package/src/components/Catalog/Catalog.tsx +1 -1
- package/src/components/Catalog/CatalogEntity/CatalogEntityHistory/CatalogEntityHistorySidebar.tsx +2 -2
- package/src/components/Catalog/CatalogEntity/CatalogEntitySchema.tsx +1 -1
- package/src/components/Catalog/CatalogMobileFiltersPanel/CatalogMobileFiltersPanel.tsx +1 -1
- package/src/components/Catalog/CatalogMobileFiltersPanel/CatalogMobileFiltersPanelOverlay.tsx +2 -2
- package/src/components/Dropdown/Dropdown.tsx +4 -1
- package/src/components/Markdown/Markdown.tsx +3 -2
- package/src/components/Menu/Menu.tsx +5 -0
- package/src/components/Menu/MenuSelect.tsx +198 -0
- package/src/components/Menu/variables.ts +43 -4
- package/src/components/Navbar/Navbar.tsx +22 -5
- package/src/components/Navbar/variables.ts +3 -0
- package/src/components/NavigationTabs/NavigationTab.tsx +148 -0
- package/src/components/NavigationTabs/NavigationTabContent.tsx +66 -0
- package/src/components/NavigationTabs/NavigationTabMenu.tsx +40 -0
- package/src/components/NavigationTabs/NavigationTabs.tsx +83 -0
- package/src/components/NavigationTabs/variables.ts +34 -0
- package/src/components/Search/SearchTrigger.tsx +11 -2
- package/src/components/Sidebar/Sidebar.tsx +2 -2
- package/src/components/TableOfContent/TableOfContent.tsx +2 -2
- package/src/core/constants/common.ts +1 -0
- package/src/core/hooks/__mocks__/use-theme-hooks.ts +2 -0
- package/src/core/hooks/index.ts +1 -0
- package/src/core/hooks/menu/use-mobile-menu-items.ts +91 -56
- package/src/core/hooks/menu/use-mobile-menu-levels.ts +14 -6
- package/src/core/hooks/use-scroll-item-into-view.ts +18 -0
- package/src/core/hooks/use-theme-hooks.ts +2 -0
- package/src/core/styles/global.ts +12 -2
- package/src/core/types/hooks.ts +5 -0
- package/src/core/types/index.ts +1 -0
- package/src/core/types/l10n.ts +3 -0
- package/src/core/types/navigation-tabs.ts +54 -0
- package/src/core/utils/index.ts +1 -0
- package/src/core/utils/menu.ts +49 -3
- package/src/core/utils/navigation-tabs.ts +99 -0
- package/src/index.ts +6 -0
- package/src/layouts/PageLayout.tsx +1 -1
- package/src/layouts/RootLayout.tsx +2 -2
- package/src/markdoc/components/CodeWalkthrough/CodeFilters.tsx +1 -1
- package/src/markdoc/components/CodeWalkthrough/CodePanel.tsx +3 -3
- package/src/markdoc/components/CodeWalkthrough/CodeWalkthrough.tsx +1 -1
|
@@ -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-
|
|
137
|
-
--menu-mobile-height: calc(100dvh - var(--navbar-
|
|
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:
|
|
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:
|
|
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 {
|
|
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
|
|
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
|
+
`;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import styled from 'styled-components';
|
|
3
|
+
|
|
4
|
+
import type { JSX } from 'react';
|
|
5
|
+
import type { NavigationTabItem } from '@redocly/theme/core/types';
|
|
6
|
+
|
|
7
|
+
import { DropdownMenu } from '@redocly/theme/components/Dropdown/DropdownMenu';
|
|
8
|
+
import { DropdownMenuItem } from '@redocly/theme/components/Dropdown/DropdownMenuItem';
|
|
9
|
+
import { NavigationTabContent } from '@redocly/theme/components/NavigationTabs/NavigationTabContent';
|
|
10
|
+
|
|
11
|
+
export type NavigationTabMenuProps = {
|
|
12
|
+
items: NavigationTabItem[];
|
|
13
|
+
className?: string;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export function NavigationTabMenu({ items, className }: NavigationTabMenuProps): JSX.Element {
|
|
17
|
+
return (
|
|
18
|
+
<NavigationTabMenuWrapper
|
|
19
|
+
data-component-name="NavigationTabs/NavigationTabMenu"
|
|
20
|
+
className={className}
|
|
21
|
+
>
|
|
22
|
+
<DropdownMenu>
|
|
23
|
+
{items.map((item, index) => (
|
|
24
|
+
<DropdownMenuItem
|
|
25
|
+
key={`${item.label}_${index}`}
|
|
26
|
+
to={item.link}
|
|
27
|
+
external={item.external}
|
|
28
|
+
data-translation-key={item.labelTranslationKey}
|
|
29
|
+
active={item.active}
|
|
30
|
+
aria-current={item.active ? 'page' : undefined}
|
|
31
|
+
>
|
|
32
|
+
<NavigationTabContent item={item} />
|
|
33
|
+
</DropdownMenuItem>
|
|
34
|
+
))}
|
|
35
|
+
</DropdownMenu>
|
|
36
|
+
</NavigationTabMenuWrapper>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const NavigationTabMenuWrapper = styled.div``;
|