@redocly/theme 0.59.0-next.1 → 0.59.0-next.3
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/LICENSE +7 -1
- package/lib/components/Accordion/Accordion.js +17 -7
- package/lib/components/Accordion/AccordionBody.js +17 -7
- package/lib/components/Admonition/Admonition.js +17 -7
- package/lib/components/Badge/Badge.js +17 -7
- package/lib/components/Breadcrumbs/Breadcrumb.js +17 -7
- package/lib/components/Breadcrumbs/BreadcrumbDropdown.js +17 -7
- package/lib/components/Button/Button.js +17 -7
- package/lib/components/Buttons/AIAssistantButton.js +17 -7
- package/lib/components/Buttons/CopyButton.js +17 -7
- package/lib/components/Catalog/Catalog.d.ts +6 -0
- package/lib/components/Catalog/Catalog.js +7 -6
- package/lib/components/Catalog/CatalogEntities.js +17 -7
- package/lib/components/Catalog/CatalogEntity/CatalogEntityGraph/CatalogEntityRelationsGraph.js +17 -7
- package/lib/components/Catalog/CatalogEntity/CatalogEntityGraph/CatalogEntityRelationsGraph.lazy.js +17 -7
- package/lib/components/Catalog/CatalogEntity/CatalogEntityMetadata.js +17 -7
- package/lib/components/Catalog/CatalogEntity/CatalogEntityRelations/CatalogEntityApiDescriptionRelations.js +1 -1
- package/lib/components/Catalog/CatalogEntity/CatalogEntityRelations/CatalogEntityRelations.js +17 -7
- package/lib/components/Catalog/CatalogEntity/CatalogEntityRelations/CatalogEntityTeamRelations.js +1 -1
- package/lib/components/Catalog/CatalogEntity/CatalogEntitySchema.js +17 -7
- package/lib/components/Catalog/CatalogFilter/CatalogFilterCheckboxes.js +17 -7
- package/lib/components/Catalog/CatalogFilter/CatalogFilterContent.js +17 -7
- package/lib/components/Catalog/CatalogFilter/CatalogFilterDateRange.js +17 -7
- package/lib/components/Catalog/CatalogFilter/CatalogFilterSelect.js +17 -7
- package/lib/components/Catalog/CatalogSortButton.js +17 -7
- package/lib/components/Catalog/CatalogTableView/CatalogTableHeaderCell.js +17 -7
- package/lib/components/Catalog/CatalogViewModeToggle.js +17 -7
- package/lib/components/CatalogClassic/CatalogClassicActions.js +17 -7
- package/lib/components/CatalogClassic/CatalogClassicCard.js +17 -7
- package/lib/components/CatalogClassic/CatalogClassicHighlight.js +17 -7
- package/lib/components/CatalogClassic/CatalogClassicVirtualizedGroups.js +17 -7
- package/lib/components/CodeBlock/CodeBlock.js +17 -7
- package/lib/components/CodeBlock/CodeBlockContainer.js +17 -7
- package/lib/components/CodeBlock/CodeBlockTabs.js +17 -7
- package/lib/components/Dropdown/Dropdown.d.ts +16 -2
- package/lib/components/Dropdown/Dropdown.js +22 -12
- package/lib/components/Dropdown/DropdownMenuItem.js +17 -7
- package/lib/components/Feedback/Comment.js +17 -7
- package/lib/components/Feedback/Feedback.js +17 -7
- package/lib/components/Feedback/Mood.js +17 -7
- package/lib/components/Feedback/Rating.js +17 -7
- package/lib/components/Feedback/Reasons.js +17 -7
- package/lib/components/Feedback/Scale.js +17 -7
- package/lib/components/Feedback/Sentiment.js +17 -7
- package/lib/components/Feedback/Stars.js +17 -7
- package/lib/components/Filter/FilterContent.js +17 -7
- package/lib/components/Filter/FilterInput.js +17 -7
- package/lib/components/Image/Image.js +17 -7
- package/lib/components/JsonViewer/JsonViewer.js +17 -7
- package/lib/components/JsonViewer/helpers.js +17 -7
- package/lib/components/LastUpdated/LastUpdated.js +17 -7
- package/lib/components/Link/Link.js +17 -7
- package/lib/components/Markdown/Markdown.js +17 -7
- package/lib/components/Marker/Marker.js +17 -7
- package/lib/components/Menu/MenuContainer.js +17 -7
- package/lib/components/Menu/MenuItem.js +18 -8
- package/lib/components/Menu/MenuMobile.js +17 -7
- package/lib/components/Navbar/NavbarItem.js +3 -3
- package/lib/components/PageActions/PageActions.js +17 -7
- package/lib/components/PageNavigation/NextButton.js +17 -7
- package/lib/components/Panel/Panel.js +17 -7
- package/lib/components/Panel/PanelBody.js +17 -7
- package/lib/components/Search/FilterFields/SearchFilterFieldSelect.js +17 -7
- package/lib/components/Search/FilterFields/SearchFilterFieldTags.js +1 -2
- package/lib/components/Search/SearchAiConversationInput.d.ts +2 -1
- package/lib/components/Search/SearchAiConversationInput.js +28 -10
- package/lib/components/Search/SearchAiDialog.js +17 -7
- package/lib/components/Search/SearchDialog.js +23 -10
- package/lib/components/Search/SearchFilter.js +17 -7
- package/lib/components/Search/SearchGroups.js +19 -9
- package/lib/components/Search/SearchHighlight.js +17 -7
- package/lib/components/Search/SearchItem.js +17 -7
- package/lib/components/Search/SearchRecent.js +17 -7
- package/lib/components/Search/SearchShortcut.js +17 -7
- package/lib/components/Search/SearchSuggestedPages.js +17 -7
- package/lib/components/Search/SearchTrigger.js +17 -7
- package/lib/components/Search/variables.js +5 -1
- package/lib/components/Segmented/Segmented.js +17 -7
- package/lib/components/Select/Select.js +17 -7
- package/lib/components/Select/SelectInput.js +18 -8
- package/lib/components/Sidebar/Sidebar.js +17 -7
- package/lib/components/SidebarActions/styled.js +17 -7
- package/lib/components/SkipContent/SkipContent.js +17 -7
- package/lib/components/Switch/Switch.js +17 -7
- package/lib/components/TableOfContent/TableOfContent.js +17 -7
- package/lib/components/Tag/Tag.d.ts +2 -1
- package/lib/components/Tag/Tag.js +67 -18
- package/lib/components/Tag/variables.dark.js +135 -36
- package/lib/components/Tag/variables.js +78 -61
- package/lib/components/Tooltip/Tooltip.js +17 -7
- package/lib/components/VersionPicker/VersionPicker.js +17 -7
- package/lib/core/constants/search.d.ts +5 -4
- package/lib/core/constants/search.js +4 -5
- package/lib/core/contexts/CodeSnippetContext.js +17 -7
- package/lib/core/hooks/use-tabs.d.ts +3 -2
- package/lib/core/hooks/use-tabs.js +115 -57
- package/lib/core/templates/Markdown.js +17 -7
- package/lib/core/types/hooks.d.ts +6 -3
- package/lib/core/types/l10n.d.ts +1 -1
- package/lib/core/utils/download-code-walkthrough.js +17 -7
- package/lib/core/utils/get-file-icon.js +17 -7
- package/lib/icons/AiStarsIcon/AiStarsIcon.js +11 -2
- package/lib/icons/GenericIcon/GenericIcon.js +17 -7
- package/lib/icons/RedoclyIcon/RedoclyIcon.js +4 -7
- package/lib/icons/Spinner/Spinner.js +17 -7
- package/lib/index.js +17 -7
- package/lib/layouts/OIDCForbidden.js +17 -7
- package/lib/layouts/ThreePanelLayout.js +17 -7
- package/lib/markdoc/components/Cards/Cards.js +17 -7
- package/lib/markdoc/components/CodeGroup/CodeGroup.js +17 -7
- package/lib/markdoc/components/CodeWalkthrough/CodeContainer.js +17 -7
- package/lib/markdoc/components/CodeWalkthrough/CodePanel.js +17 -7
- package/lib/markdoc/components/CodeWalkthrough/CodePanelHeader.js +17 -7
- package/lib/markdoc/components/CodeWalkthrough/CodePanelPreview.js +17 -7
- package/lib/markdoc/components/CodeWalkthrough/CodePanelToolbar.js +17 -7
- package/lib/markdoc/components/CodeWalkthrough/CodeStep.js +17 -7
- package/lib/markdoc/components/CodeWalkthrough/CodeToggle.js +17 -7
- package/lib/markdoc/components/CodeWalkthrough/CodeWalkthrough.js +17 -7
- package/lib/markdoc/components/CodeWalkthrough/Input.js +17 -7
- package/lib/markdoc/components/Heading/Heading.js +17 -7
- package/lib/markdoc/components/HtmlBlock/HtmlBlock.js +17 -7
- package/lib/markdoc/components/InlineSvg/InlineSvg.js +17 -7
- package/lib/markdoc/components/MarkdocExample/MarkdocExample.js +17 -7
- package/lib/markdoc/components/Tabs/TabList.d.ts +3 -1
- package/lib/markdoc/components/Tabs/TabList.js +214 -54
- package/lib/markdoc/components/Tabs/Tabs.d.ts +2 -1
- package/lib/markdoc/components/Tabs/Tabs.js +74 -19
- package/lib/markdoc/default.d.ts +104 -1
- package/lib/markdoc/default.js +17 -7
- package/package.json +6 -6
- package/src/components/Catalog/Catalog.tsx +15 -4
- package/src/components/Catalog/CatalogEntity/CatalogEntityRelations/CatalogEntityApiDescriptionRelations.tsx +1 -1
- package/src/components/Catalog/CatalogEntity/CatalogEntityRelations/CatalogEntityTeamRelations.tsx +1 -1
- package/src/components/Dropdown/Dropdown.tsx +84 -79
- package/src/components/Menu/MenuItem.tsx +1 -0
- package/src/components/Navbar/NavbarItem.tsx +6 -5
- package/src/components/Search/FilterFields/SearchFilterFieldTags.tsx +3 -3
- package/src/components/Search/SearchAiConversationInput.tsx +12 -2
- package/src/components/Search/SearchDialog.tsx +6 -3
- package/src/components/Search/SearchGroups.tsx +2 -0
- package/src/components/Search/variables.ts +5 -1
- package/src/components/Select/SelectInput.tsx +1 -0
- package/src/components/Tag/Tag.tsx +36 -20
- package/src/components/Tag/variables.dark.ts +135 -36
- package/src/components/Tag/variables.ts +78 -61
- package/src/core/constants/search.ts +8 -4
- package/src/core/hooks/use-tabs.ts +168 -86
- package/src/core/types/hooks.ts +6 -1
- package/src/core/types/l10n.ts +1 -0
- package/src/icons/AiStarsIcon/AiStarsIcon.tsx +11 -2
- package/src/icons/RedoclyIcon/RedoclyIcon.tsx +4 -22
- package/src/markdoc/components/Tabs/TabList.tsx +312 -105
- package/src/markdoc/components/Tabs/Tabs.tsx +136 -11
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, {
|
|
1
|
+
import React, { useState, useRef, useEffect, useCallback } from 'react';
|
|
2
2
|
import styled, { css } from 'styled-components';
|
|
3
3
|
|
|
4
4
|
import type { JSX } from 'react';
|
|
@@ -17,6 +17,140 @@ type TabListProps = {
|
|
|
17
17
|
size: TabsSize;
|
|
18
18
|
activeTab: string;
|
|
19
19
|
onTabChange: (tab: string) => void;
|
|
20
|
+
containerRef: React.RefObject<HTMLUListElement | null>;
|
|
21
|
+
onReadyChange?: (isReady: boolean) => void;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
type UseHighlightBarAnimationProps = {
|
|
25
|
+
childrenArray: React.ReactElement<TabItemProps>[];
|
|
26
|
+
activeTab: string;
|
|
27
|
+
tabsContainerRef: React.RefObject<HTMLElement | null>;
|
|
28
|
+
visibleTabs: number[];
|
|
29
|
+
overflowTabs: number[];
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Calculates optimal dropdown position relative to viewport to ensure visibility.
|
|
34
|
+
* Positions below the button by default, but moves above if insufficient space.
|
|
35
|
+
* Adjusts horizontal position to prevent overflow off screen edges.
|
|
36
|
+
*/
|
|
37
|
+
const calculateDropdownPosition = (
|
|
38
|
+
buttonRect: DOMRect,
|
|
39
|
+
dropdownRect: DOMRect,
|
|
40
|
+
): { top: number; left: number } => {
|
|
41
|
+
const gap = 4;
|
|
42
|
+
const margin = 16;
|
|
43
|
+
const spaceBelow = window.innerHeight - buttonRect.bottom;
|
|
44
|
+
const spaceAbove = buttonRect.top;
|
|
45
|
+
|
|
46
|
+
// Position below button, or above if dropdown doesn't fit below
|
|
47
|
+
const top =
|
|
48
|
+
spaceBelow < dropdownRect.height + gap && spaceAbove > spaceBelow
|
|
49
|
+
? buttonRect.top - gap
|
|
50
|
+
: buttonRect.bottom + gap;
|
|
51
|
+
|
|
52
|
+
// Align with button left edge, adjust if overflows screen
|
|
53
|
+
const idealLeft = buttonRect.left;
|
|
54
|
+
const rightEdge = idealLeft + dropdownRect.width;
|
|
55
|
+
const overflowsRight = rightEdge > window.innerWidth - margin;
|
|
56
|
+
|
|
57
|
+
const left = overflowsRight
|
|
58
|
+
? window.innerWidth - dropdownRect.width - margin
|
|
59
|
+
: Math.max(margin, idealLeft);
|
|
60
|
+
|
|
61
|
+
return { top, left };
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Manages dropdown positioning and updates on scroll/resize events for TabList.
|
|
66
|
+
*/
|
|
67
|
+
const useDropdownPosition = (
|
|
68
|
+
hasOverflow: boolean,
|
|
69
|
+
dropdownRef: React.RefObject<HTMLDivElement | null>,
|
|
70
|
+
) => {
|
|
71
|
+
const [dropdownPosition, setDropdownPosition] = useState<{ top?: number; left?: number }>({});
|
|
72
|
+
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
|
|
73
|
+
|
|
74
|
+
const updateDropdownPosition = useCallback(() => {
|
|
75
|
+
if (!dropdownRef.current) return;
|
|
76
|
+
|
|
77
|
+
const button = dropdownRef.current.querySelector('button');
|
|
78
|
+
const dropdownMenu = dropdownRef.current.querySelector('div:last-child');
|
|
79
|
+
if (!button || !dropdownMenu) return;
|
|
80
|
+
|
|
81
|
+
const buttonRect = button.getBoundingClientRect();
|
|
82
|
+
const dropdownRect = (dropdownMenu as HTMLElement).getBoundingClientRect();
|
|
83
|
+
|
|
84
|
+
const position = calculateDropdownPosition(buttonRect, dropdownRect);
|
|
85
|
+
setDropdownPosition(position);
|
|
86
|
+
}, [dropdownRef]);
|
|
87
|
+
|
|
88
|
+
// Track when dropdown menu appears and recalculate position
|
|
89
|
+
useEffect(() => {
|
|
90
|
+
if (!hasOverflow || !isDropdownOpen || !dropdownRef.current) return;
|
|
91
|
+
|
|
92
|
+
const dropdownMenu = dropdownRef.current.querySelector('div:last-child') as HTMLElement;
|
|
93
|
+
if (!dropdownMenu) return;
|
|
94
|
+
|
|
95
|
+
// ResizeObserver tracks both initial render and size changes
|
|
96
|
+
const resizeObserver = new ResizeObserver(() => {
|
|
97
|
+
updateDropdownPosition();
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
resizeObserver.observe(dropdownMenu);
|
|
101
|
+
|
|
102
|
+
return () => resizeObserver.disconnect();
|
|
103
|
+
}, [hasOverflow, isDropdownOpen, dropdownRef, updateDropdownPosition]);
|
|
104
|
+
|
|
105
|
+
// Update position on scroll/resize
|
|
106
|
+
useEffect(() => {
|
|
107
|
+
if (!hasOverflow || !isDropdownOpen) return;
|
|
108
|
+
|
|
109
|
+
window.addEventListener('scroll', updateDropdownPosition, true);
|
|
110
|
+
window.addEventListener('resize', updateDropdownPosition);
|
|
111
|
+
|
|
112
|
+
return () => {
|
|
113
|
+
window.removeEventListener('scroll', updateDropdownPosition, true);
|
|
114
|
+
window.removeEventListener('resize', updateDropdownPosition);
|
|
115
|
+
};
|
|
116
|
+
}, [hasOverflow, isDropdownOpen, updateDropdownPosition]);
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
dropdownPosition,
|
|
120
|
+
isDropdownOpen,
|
|
121
|
+
setIsDropdownOpen,
|
|
122
|
+
setDropdownPosition,
|
|
123
|
+
updateDropdownPosition,
|
|
124
|
+
};
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const renderTab = (
|
|
128
|
+
child: React.ReactElement<TabItemProps>,
|
|
129
|
+
index: number,
|
|
130
|
+
size: TabsSize,
|
|
131
|
+
setTabRef: (element: HTMLButtonElement | null, index: number) => void,
|
|
132
|
+
handleKeyboard: (event: React.KeyboardEvent, index: number) => void,
|
|
133
|
+
onTabClick: (labelOrIndex: string | number) => void,
|
|
134
|
+
) => {
|
|
135
|
+
const { label, icon } = child.props;
|
|
136
|
+
const tabId = getTabId(label, index);
|
|
137
|
+
|
|
138
|
+
return (
|
|
139
|
+
<Tab
|
|
140
|
+
key={`key-${tabId}`}
|
|
141
|
+
tabId={tabId}
|
|
142
|
+
label={label}
|
|
143
|
+
icon={icon}
|
|
144
|
+
size={size}
|
|
145
|
+
disabled={child.props.disable}
|
|
146
|
+
setRef={(el: HTMLButtonElement | null) => setTabRef(el, index)}
|
|
147
|
+
onKeyDown={(event) => handleKeyboard(event, index)}
|
|
148
|
+
onClick={() => {
|
|
149
|
+
child.props.onClick?.();
|
|
150
|
+
onTabClick(label);
|
|
151
|
+
}}
|
|
152
|
+
/>
|
|
153
|
+
);
|
|
20
154
|
};
|
|
21
155
|
|
|
22
156
|
export function TabList({
|
|
@@ -24,106 +158,119 @@ export function TabList({
|
|
|
24
158
|
size,
|
|
25
159
|
activeTab,
|
|
26
160
|
onTabChange,
|
|
161
|
+
containerRef,
|
|
162
|
+
onReadyChange,
|
|
27
163
|
}: TabListProps): JSX.Element {
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
164
|
+
const dropdownRef = useRef<HTMLDivElement>(null);
|
|
165
|
+
const totalTabs = childrenArray.length;
|
|
166
|
+
|
|
167
|
+
const { overflowTabs, visibleTabs, handleKeyboard, onTabClick, setTabRef, isReady } = useTabs({
|
|
168
|
+
activeTab,
|
|
169
|
+
onTabChange,
|
|
170
|
+
containerRef,
|
|
171
|
+
totalTabs,
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
useEffect(() => {
|
|
175
|
+
onReadyChange?.(isReady);
|
|
176
|
+
}, [isReady, onReadyChange]);
|
|
37
177
|
|
|
38
178
|
const { highlightStyle } = useHighlightBarAnimation({
|
|
39
179
|
activeTab,
|
|
40
180
|
childrenArray,
|
|
41
181
|
overflowTabs,
|
|
42
|
-
tabsContainerRef,
|
|
182
|
+
tabsContainerRef: containerRef,
|
|
43
183
|
visibleTabs,
|
|
44
184
|
});
|
|
45
185
|
|
|
186
|
+
const hasOverflow = overflowTabs.length > 0;
|
|
187
|
+
const isMoreActive =
|
|
188
|
+
hasOverflow &&
|
|
189
|
+
overflowTabs.some((i) => childrenArray[i] && activeTab === childrenArray[i].props.label);
|
|
190
|
+
|
|
191
|
+
// Show as selector when no visible tabs (all tabs in dropdown)
|
|
192
|
+
const showAsSelector = visibleTabs.length === 0 && hasOverflow;
|
|
193
|
+
|
|
194
|
+
const { dropdownPosition, setIsDropdownOpen, setDropdownPosition } = useDropdownPosition(
|
|
195
|
+
hasOverflow,
|
|
196
|
+
dropdownRef,
|
|
197
|
+
);
|
|
198
|
+
|
|
46
199
|
return (
|
|
47
|
-
<TabListContainer role="tablist" ref={
|
|
200
|
+
<TabListContainer role="tablist" ref={containerRef}>
|
|
48
201
|
<HighlightBar size={size} style={highlightStyle}>
|
|
49
202
|
<div />
|
|
50
203
|
</HighlightBar>
|
|
204
|
+
|
|
51
205
|
{childrenArray.map((child, index) => {
|
|
52
|
-
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
return (
|
|
56
|
-
<Tab
|
|
57
|
-
key={`key-${tabId}`}
|
|
58
|
-
tabId={tabId}
|
|
59
|
-
label={label}
|
|
60
|
-
icon={icon}
|
|
61
|
-
size={size}
|
|
62
|
-
disabled={child.props.disable}
|
|
63
|
-
setRef={(el: HTMLButtonElement | null) => setTabRef(el, index)}
|
|
64
|
-
onKeyDown={(event) => handleKeyboard(event, index)}
|
|
65
|
-
onClick={() => {
|
|
66
|
-
child.props.onClick?.();
|
|
67
|
-
onTabClick(label);
|
|
68
|
-
}}
|
|
69
|
-
/>
|
|
70
|
-
);
|
|
206
|
+
// Show all tabs before ready (for measurement), then only visible ones
|
|
207
|
+
const shouldRender = !isReady || visibleTabs.includes(index);
|
|
208
|
+
if (!shouldRender) return null;
|
|
209
|
+
return renderTab(child, index, size, setTabRef, handleKeyboard, onTabClick);
|
|
71
210
|
})}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
}
|
|
87
|
-
>
|
|
88
|
-
{allTabsHidden ? activeTab : 'More'}
|
|
89
|
-
</TabButtonLink>
|
|
90
|
-
}
|
|
91
|
-
alignment="start"
|
|
92
|
-
withArrow={true}
|
|
211
|
+
|
|
212
|
+
{hasOverflow && (
|
|
213
|
+
<TabItem
|
|
214
|
+
size={size}
|
|
215
|
+
active={isMoreActive || showAsSelector}
|
|
216
|
+
tabIndex={0}
|
|
217
|
+
className="dropdown-tab"
|
|
218
|
+
>
|
|
219
|
+
<DropdownWrapper
|
|
220
|
+
$top={dropdownPosition.top}
|
|
221
|
+
$left={dropdownPosition.left}
|
|
222
|
+
onClickCapture={() => {
|
|
223
|
+
setIsDropdownOpen(true);
|
|
224
|
+
}}
|
|
93
225
|
>
|
|
94
|
-
<
|
|
95
|
-
{
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
226
|
+
<FixedPositionDropdown
|
|
227
|
+
ref={dropdownRef}
|
|
228
|
+
trigger={
|
|
229
|
+
<TabButtonLink
|
|
230
|
+
size={size}
|
|
231
|
+
className={isMoreActive || showAsSelector ? 'active' : undefined}
|
|
232
|
+
>
|
|
233
|
+
{showAsSelector ? <TabButtonText>{activeTab}</TabButtonText> : 'More'}
|
|
234
|
+
</TabButtonLink>
|
|
235
|
+
}
|
|
236
|
+
alignment="start"
|
|
237
|
+
withArrow
|
|
238
|
+
onClose={() => {
|
|
239
|
+
setIsDropdownOpen(false);
|
|
240
|
+
setDropdownPosition({});
|
|
241
|
+
}}
|
|
242
|
+
>
|
|
243
|
+
<DropdownMenu>
|
|
244
|
+
{overflowTabs.map((index) => {
|
|
245
|
+
const child = childrenArray[index];
|
|
246
|
+
if (!child) return null;
|
|
247
|
+
|
|
248
|
+
const { label } = child.props;
|
|
249
|
+
const tabId = getTabId(label, index);
|
|
250
|
+
|
|
251
|
+
return (
|
|
252
|
+
<DropdownMenuItem
|
|
253
|
+
key={`more-${tabId}`}
|
|
254
|
+
active={activeTab === label}
|
|
255
|
+
onAction={() => {
|
|
256
|
+
child.props.onClick?.();
|
|
257
|
+
onTabClick(index);
|
|
258
|
+
}}
|
|
259
|
+
disabled={child.props.disable}
|
|
260
|
+
>
|
|
261
|
+
{label}
|
|
262
|
+
</DropdownMenuItem>
|
|
263
|
+
);
|
|
264
|
+
})}
|
|
265
|
+
</DropdownMenu>
|
|
266
|
+
</FixedPositionDropdown>
|
|
267
|
+
</DropdownWrapper>
|
|
268
|
+
</TabItem>
|
|
269
|
+
)}
|
|
116
270
|
</TabListContainer>
|
|
117
271
|
);
|
|
118
272
|
}
|
|
119
273
|
|
|
120
|
-
type UseHighlightBarAnimationProps = {
|
|
121
|
-
childrenArray: React.ReactElement<TabItemProps>[];
|
|
122
|
-
activeTab: string;
|
|
123
|
-
tabsContainerRef: React.RefObject<HTMLElement | null>;
|
|
124
|
-
visibleTabs: number[];
|
|
125
|
-
overflowTabs: number[];
|
|
126
|
-
};
|
|
127
274
|
const useHighlightBarAnimation = (props: UseHighlightBarAnimationProps) => {
|
|
128
275
|
const { childrenArray, activeTab, tabsContainerRef, visibleTabs, overflowTabs } = props;
|
|
129
276
|
|
|
@@ -141,35 +288,39 @@ const useHighlightBarAnimation = (props: UseHighlightBarAnimationProps) => {
|
|
|
141
288
|
return;
|
|
142
289
|
}
|
|
143
290
|
|
|
144
|
-
|
|
145
|
-
`[data-label="${activeTab}"]`,
|
|
146
|
-
);
|
|
147
|
-
if (!activeTabElement) return;
|
|
148
|
-
|
|
291
|
+
// Remove active class from all tabs first
|
|
149
292
|
container.querySelectorAll('[data-label]').forEach((el) => {
|
|
150
293
|
el.classList.remove('active');
|
|
151
294
|
});
|
|
152
295
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
if (visibleTabs.includes(activeIndex)) {
|
|
156
|
-
activeTabElement.classList.add('active');
|
|
157
|
-
setHighlightStyle({ left: offsetLeft, width: offsetWidth });
|
|
158
|
-
return;
|
|
159
|
-
}
|
|
160
|
-
|
|
296
|
+
// Check if active tab is in overflow first
|
|
161
297
|
if (overflowTabs.includes(activeIndex)) {
|
|
162
298
|
const moreButton = container.querySelector('button');
|
|
163
299
|
if (!moreButton) return;
|
|
164
300
|
|
|
165
301
|
const moreButtonRect = moreButton.getBoundingClientRect();
|
|
166
302
|
const containerRect = container.getBoundingClientRect();
|
|
303
|
+
|
|
167
304
|
setHighlightStyle({
|
|
168
305
|
left: moreButtonRect.left - containerRect.left,
|
|
169
306
|
width: moreButtonRect.width,
|
|
170
307
|
});
|
|
171
308
|
return;
|
|
172
309
|
}
|
|
310
|
+
|
|
311
|
+
// Active tab is visible, find its element
|
|
312
|
+
const activeTabElement: HTMLElement | null = container.querySelector(
|
|
313
|
+
`[data-label="${activeTab}"]`,
|
|
314
|
+
);
|
|
315
|
+
if (!activeTabElement) return;
|
|
316
|
+
|
|
317
|
+
const { offsetLeft, offsetWidth } = activeTabElement;
|
|
318
|
+
|
|
319
|
+
if (visibleTabs.includes(activeIndex)) {
|
|
320
|
+
activeTabElement.classList.add('active');
|
|
321
|
+
setHighlightStyle({ left: offsetLeft, width: offsetWidth });
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
173
324
|
}, [activeTab, childrenArray, visibleTabs, overflowTabs, tabsContainerRef]);
|
|
174
325
|
|
|
175
326
|
return { highlightStyle };
|
|
@@ -181,15 +332,11 @@ export const TabListContainer = styled.ul`
|
|
|
181
332
|
gap: var(--md-tabs-gap);
|
|
182
333
|
width: 100%;
|
|
183
334
|
min-width: 0;
|
|
184
|
-
position: relative;
|
|
185
335
|
|
|
186
336
|
&::before {
|
|
187
337
|
content: '';
|
|
188
338
|
position: absolute;
|
|
189
|
-
|
|
190
|
-
left: 0px;
|
|
191
|
-
right: 0px;
|
|
192
|
-
bottom: 0px;
|
|
339
|
+
inset: 0;
|
|
193
340
|
border: var(--md-tabs-border);
|
|
194
341
|
border-width: var(--md-tabs-border-width);
|
|
195
342
|
pointer-events: none;
|
|
@@ -197,11 +344,17 @@ export const TabListContainer = styled.ul`
|
|
|
197
344
|
|
|
198
345
|
&& {
|
|
199
346
|
padding: var(--md-tabs-padding);
|
|
200
|
-
margin-block-end: 0;
|
|
201
347
|
margin: 0;
|
|
202
348
|
|
|
203
349
|
& > li {
|
|
204
|
-
margin-bottom:
|
|
350
|
+
margin-bottom: 0;
|
|
351
|
+
flex-shrink: 0;
|
|
352
|
+
|
|
353
|
+
&.dropdown-tab {
|
|
354
|
+
flex-shrink: 1;
|
|
355
|
+
min-width: 0;
|
|
356
|
+
max-width: 100%;
|
|
357
|
+
}
|
|
205
358
|
}
|
|
206
359
|
}
|
|
207
360
|
`;
|
|
@@ -212,7 +365,7 @@ export const TabItem = styled.li<{ active?: boolean; size: TabsSize; tabIndex?:
|
|
|
212
365
|
cursor: pointer;
|
|
213
366
|
align-items: center;
|
|
214
367
|
padding: var(--md-tabs-tab-wrapper-padding);
|
|
215
|
-
z-index:
|
|
368
|
+
z-index: var(--z-index-surface);
|
|
216
369
|
|
|
217
370
|
${({ active, size }) =>
|
|
218
371
|
active
|
|
@@ -251,6 +404,51 @@ export const TabItem = styled.li<{ active?: boolean; size: TabsSize; tabIndex?:
|
|
|
251
404
|
}
|
|
252
405
|
`;
|
|
253
406
|
|
|
407
|
+
const DropdownWrapper = styled.div.attrs<{ $top?: number; $left?: number }>((props) => ({
|
|
408
|
+
style: {
|
|
409
|
+
...(props.$top !== undefined && { '--dropdown-top': `${props.$top}px` }),
|
|
410
|
+
...(props.$left !== undefined && { '--dropdown-left': `${props.$left}px` }),
|
|
411
|
+
},
|
|
412
|
+
}))<{ $top?: number; $left?: number }>`
|
|
413
|
+
position: static;
|
|
414
|
+
z-index: var(--z-index-raised);
|
|
415
|
+
width: 100%;
|
|
416
|
+
min-width: 0;
|
|
417
|
+
`;
|
|
418
|
+
|
|
419
|
+
const FixedPositionDropdown = styled(Dropdown)`
|
|
420
|
+
position: static;
|
|
421
|
+
width: 100%;
|
|
422
|
+
min-width: 0;
|
|
423
|
+
|
|
424
|
+
> div:first-child {
|
|
425
|
+
width: 100%;
|
|
426
|
+
min-width: 0;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
> div:last-child {
|
|
430
|
+
position: fixed;
|
|
431
|
+
top: var(--dropdown-top, 0);
|
|
432
|
+
left: var(--dropdown-left, 0);
|
|
433
|
+
right: auto;
|
|
434
|
+
bottom: auto;
|
|
435
|
+
transform: none;
|
|
436
|
+
padding-top: 0;
|
|
437
|
+
max-width: min(400px, calc(100vw - 32px));
|
|
438
|
+
max-height: calc(100vh - var(--dropdown-top, 0) - 32px);
|
|
439
|
+
overflow-y: auto;
|
|
440
|
+
z-index: var(--z-index-raised);
|
|
441
|
+
|
|
442
|
+
ul {
|
|
443
|
+
li {
|
|
444
|
+
overflow: hidden;
|
|
445
|
+
text-overflow: ellipsis;
|
|
446
|
+
white-space: nowrap;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
`;
|
|
451
|
+
|
|
254
452
|
const HighlightBar = styled.div<{ size: TabsSize }>`
|
|
255
453
|
position: absolute;
|
|
256
454
|
top: 0;
|
|
@@ -271,11 +469,20 @@ const HighlightBar = styled.div<{ size: TabsSize }>`
|
|
|
271
469
|
}
|
|
272
470
|
`;
|
|
273
471
|
|
|
472
|
+
const TabButtonText = styled.span`
|
|
473
|
+
overflow: hidden;
|
|
474
|
+
text-overflow: ellipsis;
|
|
475
|
+
white-space: nowrap;
|
|
476
|
+
flex: 1;
|
|
477
|
+
min-width: 0;
|
|
478
|
+
`;
|
|
479
|
+
|
|
274
480
|
export const TabButtonLink = styled(Button)`
|
|
275
481
|
color: var(--md-tabs-tab-text-color);
|
|
276
482
|
font-family: var(--md-tabs-tab-font-family);
|
|
277
483
|
font-style: var(--md-tabs-tab-font-style);
|
|
278
484
|
background-color: var(--md-tabs-tab-bg-color);
|
|
485
|
+
width: 100%;
|
|
279
486
|
|
|
280
487
|
transition:
|
|
281
488
|
background-color 300ms ease-in-out,
|
|
@@ -295,9 +502,9 @@ export const TabButtonLink = styled(Button)`
|
|
|
295
502
|
|
|
296
503
|
&.active {
|
|
297
504
|
color: var(--md-tabs-active-tab-text-color);
|
|
298
|
-
font-size: var(--md-tabs-${({ size }) => size}-active-tab-font-size);
|
|
299
505
|
font-family: var(--md-tabs-active-tab-font-family);
|
|
300
506
|
font-style: var(--md-tabs-active-tab-font-style);
|
|
507
|
+
font-size: var(--md-tabs-${({ size }) => size}-active-tab-font-size);
|
|
301
508
|
font-weight: var(--md-tabs-${({ size }) => size}-active-tab-font-weight);
|
|
302
509
|
line-height: var(--md-tabs-${({ size }) => size}-active-tab-line-height);
|
|
303
510
|
background-color: var(--md-tabs-active-tab-bg-color);
|
|
@@ -307,12 +514,12 @@ export const TabButtonLink = styled(Button)`
|
|
|
307
514
|
|
|
308
515
|
&:hover {
|
|
309
516
|
color: var(--md-tabs-hover-tab-text-color);
|
|
310
|
-
font-size: var(--md-tabs-${({ size }) => size}-hover-tab-font-size);
|
|
311
517
|
font-family: var(--md-tabs-hover-tab-font-family);
|
|
312
518
|
font-style: var(--md-tabs-hover-tab-font-style);
|
|
519
|
+
font-size: var(--md-tabs-${({ size }) => size}-hover-tab-font-size);
|
|
313
520
|
font-weight: var(--md-tabs-${({ size }) => size}-hover-tab-font-weight);
|
|
314
|
-
background-color: var(--md-tabs-hover-tab-bg-color);
|
|
315
521
|
line-height: var(--md-tabs-${({ size }) => size}-hover-tab-line-height);
|
|
522
|
+
background-color: var(--md-tabs-hover-tab-bg-color);
|
|
316
523
|
border-radius: var(--md-tabs-${({ size }) => size}-hover-tab-border-radius);
|
|
317
524
|
padding: var(--md-tabs-${({ size }) => size}-hover-tab-padding);
|
|
318
525
|
}
|