@redocly/theme 0.52.0-next.1 → 0.52.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/lib/components/Catalog/Catalog.js +2 -26
- package/lib/components/Catalog/CatalogCard.js +1 -0
- package/lib/components/Catalog/CatalogVirtualizedGroups.d.ts +25 -0
- package/lib/components/Catalog/CatalogVirtualizedGroups.js +183 -0
- package/lib/components/Catalog/variables.js +3 -0
- package/lib/components/Menu/MenuItem.js +1 -1
- package/lib/components/Navbar/NavbarItem.js +4 -1
- package/lib/components/Search/SearchAiMessage.js +6 -5
- package/lib/components/Search/SearchDialog.js +1 -1
- package/lib/core/hooks/__mocks__/index.d.ts +1 -0
- package/lib/core/hooks/__mocks__/index.js +1 -0
- package/lib/core/hooks/__mocks__/use-element-size.d.ts +4 -0
- package/lib/core/hooks/__mocks__/use-element-size.js +9 -0
- package/lib/core/hooks/index.d.ts +1 -0
- package/lib/core/hooks/index.js +1 -0
- package/lib/core/hooks/menu/use-nested-menu.js +5 -0
- package/lib/core/hooks/use-element-size.d.ts +13 -0
- package/lib/core/hooks/use-element-size.js +75 -0
- package/lib/core/styles/global.js +0 -1
- package/lib/core/types/index.d.ts +1 -0
- package/lib/core/types/index.js +1 -0
- package/lib/core/types/l10n.d.ts +1 -1
- package/lib/core/types/user-claims.d.ts +4 -0
- package/lib/core/types/user-claims.js +3 -0
- package/lib/ext/configure.d.ts +12 -1
- package/lib/ext/configure.js +1 -1
- package/lib/icons/CheckboxFilledIcon/CheckboxFilledIcon.d.ts +9 -0
- package/lib/icons/CheckboxFilledIcon/CheckboxFilledIcon.js +22 -0
- package/lib/icons/WarningAltFilled/WarningAltFilled.d.ts +9 -0
- package/lib/icons/WarningAltFilled/WarningAltFilled.js +23 -0
- package/lib/index.d.ts +3 -1
- package/lib/index.js +3 -1
- package/package.json +1 -1
- package/src/components/Catalog/Catalog.tsx +3 -37
- package/src/components/Catalog/CatalogCard.tsx +1 -0
- package/src/components/Catalog/CatalogVirtualizedGroups.tsx +236 -0
- package/src/components/Catalog/variables.ts +3 -0
- package/src/components/Menu/MenuItem.tsx +1 -0
- package/src/components/Navbar/NavbarItem.tsx +5 -1
- package/src/components/Search/SearchAiMessage.tsx +4 -4
- package/src/components/Search/SearchDialog.tsx +1 -1
- package/src/core/hooks/__mocks__/index.ts +1 -0
- package/src/core/hooks/__mocks__/use-element-size.ts +6 -0
- package/src/core/hooks/index.ts +1 -0
- package/src/core/hooks/menu/use-nested-menu.ts +4 -0
- package/src/core/hooks/use-element-size.ts +98 -0
- package/src/core/styles/global.ts +0 -1
- package/src/core/types/index.ts +1 -0
- package/src/core/types/l10n.ts +2 -0
- package/src/core/types/user-claims.ts +4 -0
- package/src/ext/configure.ts +14 -1
- package/src/icons/CheckboxFilledIcon/CheckboxFilledIcon.tsx +23 -0
- package/src/icons/WarningAltFilled/WarningAltFilled.tsx +24 -0
- package/src/index.ts +3 -1
- package/lib/components/Footer/FooterLogo.d.ts +0 -3
- package/lib/components/Footer/FooterLogo.js +0 -22
- package/src/components/Footer/FooterLogo.tsx +0 -20
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { IconProps } from '../../icons/types';
|
|
3
|
+
export declare const CheckboxFilledIcon: import("styled-components").StyledComponent<(props: IconProps) => React.JSX.Element, any, {
|
|
4
|
+
'data-component-name': string;
|
|
5
|
+
} & {
|
|
6
|
+
color?: string;
|
|
7
|
+
size?: string;
|
|
8
|
+
className?: string;
|
|
9
|
+
} & React.SVGProps<SVGSVGElement>, "data-component-name">;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.CheckboxFilledIcon = void 0;
|
|
7
|
+
const react_1 = __importDefault(require("react"));
|
|
8
|
+
const styled_components_1 = __importDefault(require("styled-components"));
|
|
9
|
+
const utils_1 = require("../../core/utils");
|
|
10
|
+
const Icon = (props) => (react_1.default.createElement("svg", Object.assign({ viewBox: "0 0 16 17", fill: "none", xmlns: "http://www.w3.org/2000/svg" }, props),
|
|
11
|
+
react_1.default.createElement("path", { d: "M13 2.51172H3C2.73478 2.51172 2.48043 2.61708 2.29289 2.80461C2.10536 2.99215 2 3.2465 2 3.51172V13.5117C2 13.7769 2.10536 14.0313 2.29289 14.2188C2.48043 14.4064 2.73478 14.5117 3 14.5117H13C13.2652 14.5117 13.5196 14.4064 13.7071 14.2188C13.8946 14.0313 14 13.7769 14 13.5117V3.51172C14 3.2465 13.8946 2.99215 13.7071 2.80461C13.5196 2.61708 13.2652 2.51172 13 2.51172ZM7 11.2617L4.5 8.78307L5.2954 8.01172L7 9.68452L10.7044 6.01172L11.5002 6.80032L7 11.2617Z" })));
|
|
12
|
+
exports.CheckboxFilledIcon = (0, styled_components_1.default)(Icon).attrs(() => ({
|
|
13
|
+
'data-component-name': 'icons/CheckboxFilledIcon/CheckboxFilledIcon',
|
|
14
|
+
})) `
|
|
15
|
+
path {
|
|
16
|
+
fill: ${({ color }) => (0, utils_1.getCssColorVariable)(color)};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
height: ${({ size }) => size || '16px'};
|
|
20
|
+
width: ${({ size }) => size || '16px'};
|
|
21
|
+
`;
|
|
22
|
+
//# sourceMappingURL=CheckboxFilledIcon.js.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { IconProps } from '../../icons/types';
|
|
3
|
+
export declare const WarningAltFilled: import("styled-components").StyledComponent<(props: IconProps) => React.JSX.Element, any, {
|
|
4
|
+
'data-component-name': string;
|
|
5
|
+
} & {
|
|
6
|
+
color?: string;
|
|
7
|
+
size?: string;
|
|
8
|
+
className?: string;
|
|
9
|
+
} & React.SVGProps<SVGSVGElement>, "data-component-name">;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.WarningAltFilled = void 0;
|
|
7
|
+
const react_1 = __importDefault(require("react"));
|
|
8
|
+
const styled_components_1 = __importDefault(require("styled-components"));
|
|
9
|
+
const utils_1 = require("../../core/utils");
|
|
10
|
+
const Icon = (props) => (react_1.default.createElement("svg", Object.assign({ viewBox: "0 0 16 16", fill: "none", xmlns: "http://www.w3.org/2000/svg" }, props),
|
|
11
|
+
react_1.default.createElement("path", { d: "M8.001 3.08571H7.999L2.32435 13.9983L2.3253 14H13.6747L13.6757 13.9983L8.001 3.08571ZM7.4375 6.00001H8.5625V10.5H7.4375V6.00001ZM8 13C7.85167 13 7.70666 12.956 7.58333 12.8736C7.45999 12.7912 7.36386 12.6741 7.30709 12.537C7.25033 12.4 7.23548 12.2492 7.26441 12.1037C7.29335 11.9582 7.36478 11.8246 7.46967 11.7197C7.57456 11.6148 7.7082 11.5434 7.85369 11.5144C7.99917 11.4855 8.14997 11.5003 8.28702 11.5571C8.42406 11.6139 8.54119 11.71 8.62361 11.8333C8.70602 11.9567 8.75 12.1017 8.75 12.25C8.75 12.4489 8.67099 12.6397 8.53033 12.7803C8.38968 12.921 8.19892 13 8 13Z" }),
|
|
12
|
+
react_1.default.createElement("path", { d: "M14.5 15H1.5C1.4141 15 1.32965 14.9779 1.25478 14.9357C1.17992 14.8936 1.11718 14.8329 1.0726 14.7595C1.02802 14.686 1.00311 14.6024 1.00027 14.5165C0.997436 14.4307 1.01677 14.3455 1.0564 14.2693L7.5564 1.76931C7.59862 1.68812 7.66231 1.62008 7.74053 1.5726C7.81875 1.52511 7.9085 1.5 8 1.5C8.09151 1.5 8.18126 1.52511 8.25948 1.5726C8.3377 1.62008 8.40138 1.68812 8.4436 1.76931L14.9436 14.2693C14.9832 14.3455 15.0026 14.4307 14.9997 14.5165C14.9969 14.6024 14.972 14.686 14.9274 14.7595C14.8828 14.8329 14.8201 14.8936 14.7452 14.9357C14.6704 14.9779 14.5859 15 14.5 15ZM2.3253 14H13.6747L13.6757 13.9983L8.001 3.08571H7.999L2.32435 13.9983L2.3253 14Z" })));
|
|
13
|
+
exports.WarningAltFilled = (0, styled_components_1.default)(Icon).attrs(() => ({
|
|
14
|
+
'data-component-name': 'icons/WarningAltFilled/WarningAltFilled',
|
|
15
|
+
})) `
|
|
16
|
+
path {
|
|
17
|
+
fill: ${({ color }) => (0, utils_1.getCssColorVariable)(color)};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
height: ${({ size }) => size || '16px'};
|
|
21
|
+
width: ${({ size }) => size || '16px'};
|
|
22
|
+
`;
|
|
23
|
+
//# sourceMappingURL=WarningAltFilled.js.map
|
package/lib/index.d.ts
CHANGED
|
@@ -39,7 +39,6 @@ export * from './components/Footer/Footer';
|
|
|
39
39
|
export * from './components/Footer/FooterColumn';
|
|
40
40
|
export * from './components/Footer/FooterCopyright';
|
|
41
41
|
export * from './components/Footer/FooterItem';
|
|
42
|
-
export * from './components/Footer/FooterLogo';
|
|
43
42
|
export * from './components/Typography/CompactTypography';
|
|
44
43
|
export * from './components/Typography/Emphasis';
|
|
45
44
|
export * from './components/Typography/H1';
|
|
@@ -102,6 +101,7 @@ export * from './components/Catalog/CatalogCard';
|
|
|
102
101
|
export * from './components/Catalog/CatalogActions';
|
|
103
102
|
export * from './components/Catalog/CatalogHighlight';
|
|
104
103
|
export * from './components/Catalog/CatalogInfoBlock';
|
|
104
|
+
export * from './components/Catalog/CatalogVirtualizedGroups';
|
|
105
105
|
export * from './components/Product/Product';
|
|
106
106
|
export * from './components/Product/ProductPicker';
|
|
107
107
|
export * from './components/StatusCode/StatusCode';
|
|
@@ -220,6 +220,8 @@ export * from './icons/FlowIcon/FlowIcon';
|
|
|
220
220
|
export * from './icons/DraggableIcon/DraggableIcon';
|
|
221
221
|
export * from './icons/WarningAltFilledIcon/WarningAltFilledIcon';
|
|
222
222
|
export * from './icons/ErrorFilledIcon/ErrorFilledIcon';
|
|
223
|
+
export * from './icons/CheckboxFilledIcon/CheckboxFilledIcon';
|
|
224
|
+
export * from './icons/WarningAltFilled/WarningAltFilled';
|
|
223
225
|
export * from './icons/SettingsCogIcon/SettingsCogIcon';
|
|
224
226
|
export * from './layouts/RootLayout';
|
|
225
227
|
export * from './layouts/PageLayout';
|
package/lib/index.js
CHANGED
|
@@ -75,7 +75,6 @@ __exportStar(require("./components/Footer/Footer"), exports);
|
|
|
75
75
|
__exportStar(require("./components/Footer/FooterColumn"), exports);
|
|
76
76
|
__exportStar(require("./components/Footer/FooterCopyright"), exports);
|
|
77
77
|
__exportStar(require("./components/Footer/FooterItem"), exports);
|
|
78
|
-
__exportStar(require("./components/Footer/FooterLogo"), exports);
|
|
79
78
|
/* Typography */
|
|
80
79
|
__exportStar(require("./components/Typography/CompactTypography"), exports);
|
|
81
80
|
__exportStar(require("./components/Typography/Emphasis"), exports);
|
|
@@ -150,6 +149,7 @@ __exportStar(require("./components/Catalog/CatalogCard"), exports);
|
|
|
150
149
|
__exportStar(require("./components/Catalog/CatalogActions"), exports);
|
|
151
150
|
__exportStar(require("./components/Catalog/CatalogHighlight"), exports);
|
|
152
151
|
__exportStar(require("./components/Catalog/CatalogInfoBlock"), exports);
|
|
152
|
+
__exportStar(require("./components/Catalog/CatalogVirtualizedGroups"), exports);
|
|
153
153
|
/* Product */
|
|
154
154
|
__exportStar(require("./components/Product/Product"), exports);
|
|
155
155
|
__exportStar(require("./components/Product/ProductPicker"), exports);
|
|
@@ -272,6 +272,8 @@ __exportStar(require("./icons/FlowIcon/FlowIcon"), exports);
|
|
|
272
272
|
__exportStar(require("./icons/DraggableIcon/DraggableIcon"), exports);
|
|
273
273
|
__exportStar(require("./icons/WarningAltFilledIcon/WarningAltFilledIcon"), exports);
|
|
274
274
|
__exportStar(require("./icons/ErrorFilledIcon/ErrorFilledIcon"), exports);
|
|
275
|
+
__exportStar(require("./icons/CheckboxFilledIcon/CheckboxFilledIcon"), exports);
|
|
276
|
+
__exportStar(require("./icons/WarningAltFilled/WarningAltFilled"), exports);
|
|
275
277
|
__exportStar(require("./icons/SettingsCogIcon/SettingsCogIcon"), exports);
|
|
276
278
|
/* Layouts */
|
|
277
279
|
__exportStar(require("./layouts/RootLayout"), exports);
|
package/package.json
CHANGED
|
@@ -6,13 +6,12 @@ import type { CatalogConfig } from '@redocly/theme/config';
|
|
|
6
6
|
import { breakpoints } from '@redocly/theme/core/utils';
|
|
7
7
|
import { useThemeHooks, useModalScrollLock } from '@redocly/theme/core/hooks';
|
|
8
8
|
import { H2 } from '@redocly/theme/components/Typography/H2';
|
|
9
|
-
import { CatalogCard } from '@redocly/theme/components/Catalog/CatalogCard';
|
|
10
9
|
import { FilterContent } from '@redocly/theme/components/Filter/FilterContent';
|
|
11
10
|
import { FilterPopover } from '@redocly/theme/components/Filter/FilterPopover';
|
|
12
11
|
import { HighlightContext } from '@redocly/theme/components/Catalog/CatalogHighlight';
|
|
13
12
|
import { CatalogActions } from '@redocly/theme/components/Catalog/CatalogActions';
|
|
14
13
|
import { Sidebar } from '@redocly/theme/components/Sidebar/Sidebar';
|
|
15
|
-
import {
|
|
14
|
+
import { CatalogVirtualizedGroups } from '@redocly/theme/components/Catalog/CatalogVirtualizedGroups';
|
|
16
15
|
|
|
17
16
|
export type CatalogProps = {
|
|
18
17
|
pageProps: {
|
|
@@ -73,40 +72,14 @@ export default function Catalog(props: CatalogProps): JSX.Element {
|
|
|
73
72
|
</CatalogDescription>
|
|
74
73
|
) : null}
|
|
75
74
|
</CatalogPageDescriptionWrapper>
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
<CatalogSeparator data-testid="catalog-separator">
|
|
79
|
-
<CatalogSeparatorLabel>{group.title}</CatalogSeparatorLabel>
|
|
80
|
-
<CounterTag borderless>{group.items.length}</CounterTag>
|
|
81
|
-
</CatalogSeparator>
|
|
82
|
-
<CatalogCards>
|
|
83
|
-
{group.items.map((item) => (
|
|
84
|
-
<CatalogCard item={item} key={item.link} />
|
|
85
|
-
))}
|
|
86
|
-
</CatalogCards>
|
|
87
|
-
</React.Fragment>
|
|
88
|
-
))}
|
|
75
|
+
|
|
76
|
+
<CatalogVirtualizedGroups groups={groups} filters={filters} filterTerm={filterTerm} />
|
|
89
77
|
</CatalogPageContent>
|
|
90
78
|
</CatalogPageWrapper>
|
|
91
79
|
</HighlightContext.Provider>
|
|
92
80
|
);
|
|
93
81
|
}
|
|
94
82
|
|
|
95
|
-
const CatalogSeparator = styled.div`
|
|
96
|
-
display: flex;
|
|
97
|
-
align-items: center;
|
|
98
|
-
color: var(--catalog-separator-color);
|
|
99
|
-
font-size: var(--catalog-separator-font-size);
|
|
100
|
-
font-weight: var(--catalog-separator-font-weight);
|
|
101
|
-
border-top: 1px solid var(--catalog-separator-border-color);
|
|
102
|
-
margin: var(--catalog-separator-margin);
|
|
103
|
-
padding: var(--catalog-separator-padding);
|
|
104
|
-
`;
|
|
105
|
-
|
|
106
|
-
const CatalogSeparatorLabel = styled.div`
|
|
107
|
-
margin: var(--catalog-separator-label-margin);
|
|
108
|
-
`;
|
|
109
|
-
|
|
110
83
|
export const CatalogPageContent = styled.main`
|
|
111
84
|
flex: 1;
|
|
112
85
|
width: 90%;
|
|
@@ -118,13 +91,6 @@ export const CatalogPageContent = styled.main`
|
|
|
118
91
|
}
|
|
119
92
|
`;
|
|
120
93
|
|
|
121
|
-
const CatalogCards = styled.div`
|
|
122
|
-
display: grid;
|
|
123
|
-
grid-template-columns: repeat(auto-fill, minmax(var(--api-catalog-card-min-width), 1fr));
|
|
124
|
-
gap: 32px;
|
|
125
|
-
margin: var(--catalog-cards-group-margin);
|
|
126
|
-
`;
|
|
127
|
-
|
|
128
94
|
export const CatalogTitle = styled(H2)`
|
|
129
95
|
color: var(--catalog-title-text-color);
|
|
130
96
|
font-weight: var(--catalog-title-font-weight);
|
|
@@ -119,6 +119,7 @@ const CardDescription = styled.div`
|
|
|
119
119
|
font-size: var(--catalog-card-description-font-size);
|
|
120
120
|
font-weight: var(--catalog-card-description-font-weight);
|
|
121
121
|
line-height: var(--catalog-card-description-line-height);
|
|
122
|
+
height: var(--catalog-card-description-height);
|
|
122
123
|
`;
|
|
123
124
|
|
|
124
125
|
const CardContent = styled.div`
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import React, { useMemo, useEffect, useState } from 'react';
|
|
2
|
+
import { useWindowVirtualizer } from '@tanstack/react-virtual';
|
|
3
|
+
import styled from 'styled-components';
|
|
4
|
+
|
|
5
|
+
import { CatalogItem, ResolvedFilter } from '@redocly/theme/core/types';
|
|
6
|
+
import { useElementSize } from '@redocly/theme/core/hooks';
|
|
7
|
+
import { CatalogCard } from '@redocly/theme/components/Catalog/CatalogCard';
|
|
8
|
+
import { CounterTag } from '@redocly/theme/components/Tags/CounterTag';
|
|
9
|
+
import { SpinnerLoader } from '@redocly/theme/components/Loaders/SpinnerLoader';
|
|
10
|
+
|
|
11
|
+
const GAP_SIZE = 32;
|
|
12
|
+
const ESTIMATED_HEADER_HEIGHT = 43;
|
|
13
|
+
const ESTIMATED_CARD_HEIGHT = 194 + GAP_SIZE;
|
|
14
|
+
const CARD_MIN_WIDTH_VAR = '--catalog-card-min-width';
|
|
15
|
+
|
|
16
|
+
export type Group = {
|
|
17
|
+
title: string;
|
|
18
|
+
items: CatalogItem[];
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export type VirtualRowData =
|
|
22
|
+
| { type: 'header'; groupTitle: string; groupCount: number; key: string }
|
|
23
|
+
| { type: 'cardRow'; groupTitle: string; items: CatalogItem[]; key: string };
|
|
24
|
+
|
|
25
|
+
export type CatalogVirtualizedGroupsProps = {
|
|
26
|
+
groups: Group[];
|
|
27
|
+
filters: (ResolvedFilter & { isFilterUsed?: boolean })[];
|
|
28
|
+
filterTerm: string;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export function CatalogVirtualizedGroups({
|
|
32
|
+
groups,
|
|
33
|
+
filters,
|
|
34
|
+
filterTerm,
|
|
35
|
+
}: CatalogVirtualizedGroupsProps) {
|
|
36
|
+
const [isClient, setIsClient] = useState(false);
|
|
37
|
+
const [size, parentRef] = useElementSize<HTMLDivElement>({ delay: 50, detectSizes: 'width' });
|
|
38
|
+
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
setIsClient(true);
|
|
41
|
+
}, []);
|
|
42
|
+
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
if (!size.width) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
virtualizer.measure();
|
|
49
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
50
|
+
}, [filters, filterTerm, size.width]);
|
|
51
|
+
|
|
52
|
+
const columnCount = useMemo(() => {
|
|
53
|
+
if (!size.width) return 4;
|
|
54
|
+
const cardMinWidth = parseInt(
|
|
55
|
+
getComputedStyle(document.documentElement).getPropertyValue(CARD_MIN_WIDTH_VAR),
|
|
56
|
+
10,
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
return Math.max(1, Math.floor((size.width + GAP_SIZE) / (cardMinWidth + GAP_SIZE)));
|
|
60
|
+
}, [size.width]);
|
|
61
|
+
|
|
62
|
+
const flatRows: VirtualRowData[] = useMemo(() => {
|
|
63
|
+
if (!isClient) {
|
|
64
|
+
return groups.flatMap((group) => [
|
|
65
|
+
{
|
|
66
|
+
type: 'header',
|
|
67
|
+
groupTitle: group.title,
|
|
68
|
+
groupCount: group.items.length,
|
|
69
|
+
key: `header-${group.title}`,
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
type: 'cardRow',
|
|
73
|
+
groupTitle: group.title,
|
|
74
|
+
items: group.items,
|
|
75
|
+
key: `${group.title}-cards`,
|
|
76
|
+
},
|
|
77
|
+
]);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const rows: VirtualRowData[] = [];
|
|
81
|
+
groups.forEach((group) => {
|
|
82
|
+
rows.push({
|
|
83
|
+
type: 'header',
|
|
84
|
+
groupTitle: group.title,
|
|
85
|
+
groupCount: group.items.length,
|
|
86
|
+
key: `header-${group.title}`,
|
|
87
|
+
});
|
|
88
|
+
const numRows = Math.ceil(group.items.length / columnCount);
|
|
89
|
+
for (let rowIndex = 0; rowIndex < numRows; rowIndex++) {
|
|
90
|
+
const startIndex = rowIndex * columnCount;
|
|
91
|
+
const rowItems = group.items.slice(startIndex, startIndex + columnCount);
|
|
92
|
+
rows.push({
|
|
93
|
+
type: 'cardRow',
|
|
94
|
+
groupTitle: group.title,
|
|
95
|
+
items: rowItems,
|
|
96
|
+
key: `${group.title}-row-${rowIndex}`,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
return rows;
|
|
101
|
+
}, [groups, columnCount, isClient]);
|
|
102
|
+
|
|
103
|
+
const virtualizer = useWindowVirtualizer({
|
|
104
|
+
count: flatRows.length,
|
|
105
|
+
estimateSize: (index: number) => {
|
|
106
|
+
const row = flatRows[index];
|
|
107
|
+
if (row.type === 'header') return ESTIMATED_HEADER_HEIGHT;
|
|
108
|
+
return ESTIMATED_CARD_HEIGHT;
|
|
109
|
+
},
|
|
110
|
+
overscan: 5,
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
if (!isClient) {
|
|
114
|
+
return (
|
|
115
|
+
<div ref={parentRef} data-component-name="Catalog/CatalogVirtualizedGroups">
|
|
116
|
+
{flatRows.slice(0, 15).map((rowData) => {
|
|
117
|
+
if (rowData.type === 'header') {
|
|
118
|
+
return (
|
|
119
|
+
<SSRHeaderRow key={rowData.key}>
|
|
120
|
+
<CatalogSeparatorLabel>{rowData.groupTitle}</CatalogSeparatorLabel>
|
|
121
|
+
<CounterTag borderless>{rowData.groupCount}</CounterTag>
|
|
122
|
+
</SSRHeaderRow>
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return (
|
|
127
|
+
<SSRRow key={rowData.key}>
|
|
128
|
+
{rowData.items.map((item) => (
|
|
129
|
+
<CardWrapper key={item.link}>
|
|
130
|
+
<CatalogCard item={item} />
|
|
131
|
+
</CardWrapper>
|
|
132
|
+
))}
|
|
133
|
+
</SSRRow>
|
|
134
|
+
);
|
|
135
|
+
})}
|
|
136
|
+
<LoadingWrapper>
|
|
137
|
+
<SpinnerLoader color="var(--catalog-description-text-color)" size="20px" />
|
|
138
|
+
</LoadingWrapper>
|
|
139
|
+
</div>
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return (
|
|
144
|
+
<div ref={parentRef} data-component-name="Catalog/CatalogVirtualizedGroups">
|
|
145
|
+
<div
|
|
146
|
+
style={{
|
|
147
|
+
position: 'relative',
|
|
148
|
+
height: `${virtualizer.getTotalSize()}px`,
|
|
149
|
+
}}
|
|
150
|
+
>
|
|
151
|
+
{virtualizer.getVirtualItems().map((virtualRow) => {
|
|
152
|
+
const rowData = flatRows[virtualRow.index];
|
|
153
|
+
if (rowData.type === 'header') {
|
|
154
|
+
return (
|
|
155
|
+
<HeaderRow
|
|
156
|
+
key={rowData.key}
|
|
157
|
+
ref={virtualizer.measureElement}
|
|
158
|
+
data-index={virtualRow.index}
|
|
159
|
+
style={{ transform: `translateY(${virtualRow.start}px)` }}
|
|
160
|
+
>
|
|
161
|
+
<CatalogSeparatorLabel>{rowData.groupTitle}</CatalogSeparatorLabel>
|
|
162
|
+
<CounterTag borderless>{rowData.groupCount}</CounterTag>
|
|
163
|
+
</HeaderRow>
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return (
|
|
168
|
+
<GridRow
|
|
169
|
+
key={rowData.key}
|
|
170
|
+
ref={virtualizer.measureElement}
|
|
171
|
+
data-index={virtualRow.index}
|
|
172
|
+
style={{ transform: `translateY(${virtualRow.start}px)` }}
|
|
173
|
+
>
|
|
174
|
+
{rowData.items.map((item) => (
|
|
175
|
+
<CatalogCard key={item.link} item={item} />
|
|
176
|
+
))}
|
|
177
|
+
</GridRow>
|
|
178
|
+
);
|
|
179
|
+
})}
|
|
180
|
+
</div>
|
|
181
|
+
</div>
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const SSRHeaderRow = styled.div`
|
|
186
|
+
width: 100%;
|
|
187
|
+
display: flex;
|
|
188
|
+
align-items: center;
|
|
189
|
+
padding: var(--catalog-separator-padding);
|
|
190
|
+
border-top: 1px solid var(--catalog-separator-border-color);
|
|
191
|
+
padding-bottom: calc(4px * 4);
|
|
192
|
+
color: var(--catalog-separator-color);
|
|
193
|
+
font-size: var(--catalog-separator-font-size);
|
|
194
|
+
font-weight: var(--catalog-separator-font-weight);
|
|
195
|
+
`;
|
|
196
|
+
|
|
197
|
+
const SSRRow = styled.div`
|
|
198
|
+
width: 100%;
|
|
199
|
+
display: flex;
|
|
200
|
+
flex-wrap: wrap;
|
|
201
|
+
gap: var(--catalog-cards-group-gap, 32px);
|
|
202
|
+
padding-bottom: var(--catalog-cards-group-gap, 32px);
|
|
203
|
+
`;
|
|
204
|
+
|
|
205
|
+
const HeaderRow = styled(SSRHeaderRow)`
|
|
206
|
+
position: absolute;
|
|
207
|
+
left: 0;
|
|
208
|
+
will-change: transform;
|
|
209
|
+
`;
|
|
210
|
+
|
|
211
|
+
const GridRow = styled.div`
|
|
212
|
+
position: absolute;
|
|
213
|
+
left: 0;
|
|
214
|
+
width: 100%;
|
|
215
|
+
display: grid;
|
|
216
|
+
grid-template-columns: repeat(auto-fill, minmax(var(--catalog-card-min-width), 1fr));
|
|
217
|
+
gap: var(--catalog-cards-group-gap, 32px);
|
|
218
|
+
padding-bottom: var(--catalog-cards-group-gap, 32px);
|
|
219
|
+
will-change: transform;
|
|
220
|
+
`;
|
|
221
|
+
|
|
222
|
+
const CardWrapper = styled.div`
|
|
223
|
+
flex: 1 0 var(--catalog-card-min-width);
|
|
224
|
+
max-width: 100%;
|
|
225
|
+
min-width: var(--catalog-card-min-width);
|
|
226
|
+
`;
|
|
227
|
+
|
|
228
|
+
const CatalogSeparatorLabel = styled.div`
|
|
229
|
+
margin: var(--catalog-separator-label-margin);
|
|
230
|
+
`;
|
|
231
|
+
|
|
232
|
+
const LoadingWrapper = styled.div`
|
|
233
|
+
display: flex;
|
|
234
|
+
justify-content: center;
|
|
235
|
+
align-items: center;
|
|
236
|
+
`;
|
|
@@ -45,6 +45,7 @@ export const catalog = css`
|
|
|
45
45
|
* @tokens Catalog card
|
|
46
46
|
*/
|
|
47
47
|
--catalog-card-min-height: 194px;
|
|
48
|
+
--catalog-card-min-width: 260px;
|
|
48
49
|
--catalog-card-padding-vertical: var(--spacing-base);
|
|
49
50
|
--catalog-card-padding-horizontal: var(--spacing-md);
|
|
50
51
|
--catalog-card-gap: var(--spacing-sm);
|
|
@@ -58,6 +59,7 @@ export const catalog = css`
|
|
|
58
59
|
--catalog-card-border-color-hover: var(--border-color-primary);
|
|
59
60
|
--catalog-card-border-radius: var(--border-radius-xxl);
|
|
60
61
|
--catalog-cards-group-margin: 0 0 var(--spacing-base) 0;
|
|
62
|
+
--catalog-cards-group-gap: var(--spacing-xl);
|
|
61
63
|
|
|
62
64
|
/**
|
|
63
65
|
* @tokens Catalog card title
|
|
@@ -77,6 +79,7 @@ export const catalog = css`
|
|
|
77
79
|
--catalog-card-description-font-size: var(--font-size-base);
|
|
78
80
|
--catalog-card-description-line-height: var(--line-height-base);
|
|
79
81
|
--catalog-card-description-font-weight: var(--font-weight-regular);
|
|
82
|
+
--catalog-card-description-height: 62px;
|
|
80
83
|
|
|
81
84
|
--catalog-card-content-gap: var(--spacing-xxs);
|
|
82
85
|
|
|
@@ -65,6 +65,7 @@ export function MenuItem(props: React.PropsWithChildren<MenuItemProps>): JSX.Ele
|
|
|
65
65
|
onClick={handleOnClick}
|
|
66
66
|
ref={labelRef}
|
|
67
67
|
role={item.link ? 'none' : 'link'}
|
|
68
|
+
data-testid="menu-item-label"
|
|
68
69
|
>
|
|
69
70
|
{chevron}
|
|
70
71
|
{item.icon ? <MenuItemIcon src={item.icon} /> : null}
|
|
@@ -70,7 +70,11 @@ export function NavbarItem({ navItem, className }: NavbarItemProps): JSX.Element
|
|
|
70
70
|
separatorLine={item.separatorLine}
|
|
71
71
|
data-translation-key={item.labelTranslationKey}
|
|
72
72
|
>
|
|
73
|
-
{
|
|
73
|
+
<NavbarIcon url={item.icon} />
|
|
74
|
+
<NavbarLabel data-translation-key={item.labelTranslationKey}>
|
|
75
|
+
{translate(item.labelTranslationKey, item.label)}
|
|
76
|
+
</NavbarLabel>
|
|
77
|
+
{item.external ? <ExternalLinkIcon size="10px" /> : null}
|
|
74
78
|
</DropdownMenuItem>
|
|
75
79
|
);
|
|
76
80
|
});
|
|
@@ -54,7 +54,8 @@ export function SearchAiMessage({
|
|
|
54
54
|
{!isThinking && resources && resources.length > 0 && (
|
|
55
55
|
<ResourcesWrapper data-testid="resources-wrapper">
|
|
56
56
|
<ResourcesTitle data-translation-key="search.ai.resourcesFound">
|
|
57
|
-
{
|
|
57
|
+
{translate('search.ai.resourcesFound.basedOn', 'Based on')} {resources.length}{' '}
|
|
58
|
+
{translate('search.ai.resourcesFound.resources', 'resources')}
|
|
58
59
|
</ResourcesTitle>
|
|
59
60
|
<ResourceTagsWrapper>
|
|
60
61
|
{resources.map((resource, idx) => (
|
|
@@ -156,7 +157,6 @@ const ResourceTag = styled(Tag)`
|
|
|
156
157
|
--tag-color: var(--search-ai-resource-tag-text-color);
|
|
157
158
|
max-width: 100%;
|
|
158
159
|
overflow: hidden;
|
|
159
|
-
white-space: nowrap;
|
|
160
160
|
display: inline-block;
|
|
161
161
|
}
|
|
162
162
|
svg {
|
|
@@ -166,8 +166,8 @@ const ResourceTag = styled(Tag)`
|
|
|
166
166
|
}
|
|
167
167
|
> div {
|
|
168
168
|
overflow: hidden;
|
|
169
|
-
|
|
170
|
-
white-space:
|
|
169
|
+
word-break: break-word;
|
|
170
|
+
white-space: normal;
|
|
171
171
|
max-width: 100%;
|
|
172
172
|
}
|
|
173
173
|
`;
|
|
@@ -265,7 +265,7 @@ export function SearchDialog({ onClose, className }: SearchDialogProps): JSX.Ele
|
|
|
265
265
|
) : null,
|
|
266
266
|
)
|
|
267
267
|
) : isSearchLoading ? (
|
|
268
|
-
<SearchMessage>
|
|
268
|
+
<SearchMessage data-translation-key="search.loading">
|
|
269
269
|
<SpinnerLoader size="26px" color="var(--search-input-icon-color)" />
|
|
270
270
|
{translate('search.loading', 'Loading...')}
|
|
271
271
|
</SearchMessage>
|
|
@@ -23,3 +23,4 @@ export * from '@redocly/theme/core/hooks/feedback/use-report-dialog';
|
|
|
23
23
|
export * from '@redocly/theme/core/hooks/use-product-picker';
|
|
24
24
|
export * from '@redocly/theme/core/hooks/search/use-search-dialog';
|
|
25
25
|
export * from '@redocly/theme/core/hooks/use-language-picker';
|
|
26
|
+
export * from '@redocly/theme/core/hooks/__mocks__/use-element-size';
|
package/src/core/hooks/index.ts
CHANGED
|
@@ -33,3 +33,4 @@ export * from '@redocly/theme/core/hooks/code-walkthrough/use-code-walkthrough-s
|
|
|
33
33
|
export * from '@redocly/theme/core/hooks/code-walkthrough/use-code-walkthrough-controls';
|
|
34
34
|
export * from '@redocly/theme/core/hooks/code-walkthrough/use-code-panel';
|
|
35
35
|
export * from '@redocly/theme/core/hooks/code-walkthrough/use-renderable-files';
|
|
36
|
+
export * from '@redocly/theme/core/hooks/use-element-size';
|
|
@@ -29,6 +29,10 @@ export function useNestedMenu({ item, labelRef, nestedMenuRef }: NestedMenuProps
|
|
|
29
29
|
if (state === 'expandStart') {
|
|
30
30
|
setCanUnmount(false);
|
|
31
31
|
}
|
|
32
|
+
// signal that used in e2e tests to wait for the item to be expanded
|
|
33
|
+
if (state === 'expandEnd') {
|
|
34
|
+
labelRef?.current?.dispatchEvent(new CustomEvent('menu:expand-end', { bubbles: true }));
|
|
35
|
+
}
|
|
32
36
|
},
|
|
33
37
|
});
|
|
34
38
|
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { useState, useCallback, RefObject, useLayoutEffect, useRef, MutableRefObject } from 'react';
|
|
2
|
+
|
|
3
|
+
type Size = {
|
|
4
|
+
width: number;
|
|
5
|
+
height: number;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
const getInitialSize = (ref?: RefObject<HTMLElement>): Size => {
|
|
9
|
+
if (ref?.current) {
|
|
10
|
+
return {
|
|
11
|
+
width: ref.current.offsetWidth,
|
|
12
|
+
height: ref.current.offsetHeight,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (ref?.current === null) {
|
|
17
|
+
return {
|
|
18
|
+
width: 0,
|
|
19
|
+
height: 0,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return {
|
|
24
|
+
width: typeof window !== 'undefined' ? window.innerWidth : 0,
|
|
25
|
+
height: typeof window !== 'undefined' ? window.innerHeight : 0,
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export function useElementSize<T extends HTMLElement = HTMLElement>({
|
|
30
|
+
delay = 50,
|
|
31
|
+
detectSizes = 'both',
|
|
32
|
+
}: { delay?: number; detectSizes?: 'width' | 'height' | 'both' } = {}): [
|
|
33
|
+
Size,
|
|
34
|
+
MutableRefObject<T | null>,
|
|
35
|
+
] {
|
|
36
|
+
const ref = useRef<T | null>(null);
|
|
37
|
+
const previousSize = useRef<Size>(getInitialSize(ref));
|
|
38
|
+
const [size, setSize] = useState<Size>(getInitialSize(ref));
|
|
39
|
+
const isFirstUpdate = useRef(true);
|
|
40
|
+
|
|
41
|
+
const updateSize = useCallback(
|
|
42
|
+
(newSize: Size) => {
|
|
43
|
+
const shouldUpdateWidth = detectSizes === 'both' || detectSizes === 'width';
|
|
44
|
+
const shouldUpdateHeight = detectSizes === 'both' || detectSizes === 'height';
|
|
45
|
+
|
|
46
|
+
const widthChanged = shouldUpdateWidth && newSize.width !== previousSize.current.width;
|
|
47
|
+
const heightChanged = shouldUpdateHeight && newSize.height !== previousSize.current.height;
|
|
48
|
+
|
|
49
|
+
if (widthChanged || heightChanged) {
|
|
50
|
+
const updatedSize = {
|
|
51
|
+
width: shouldUpdateWidth ? newSize.width : previousSize.current.width,
|
|
52
|
+
height: shouldUpdateHeight ? newSize.height : previousSize.current.height,
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
setSize(updatedSize);
|
|
56
|
+
previousSize.current = updatedSize;
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
[detectSizes],
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
useLayoutEffect(() => {
|
|
63
|
+
let timeoutId: number | null = null;
|
|
64
|
+
|
|
65
|
+
const triggerUpdateWithThrottling = (newSize: Size) => {
|
|
66
|
+
if (isFirstUpdate.current) {
|
|
67
|
+
updateSize(newSize);
|
|
68
|
+
isFirstUpdate.current = false;
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (timeoutId !== null) return;
|
|
73
|
+
timeoutId = window.setTimeout(() => {
|
|
74
|
+
updateSize(newSize);
|
|
75
|
+
timeoutId = null;
|
|
76
|
+
}, delay);
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
if (ref?.current && typeof ResizeObserver !== 'undefined') {
|
|
80
|
+
const observer = new ResizeObserver((entries) => {
|
|
81
|
+
for (const entry of entries) {
|
|
82
|
+
const { width, height } = entry.contentRect;
|
|
83
|
+
triggerUpdateWithThrottling({ width, height });
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
observer.observe(ref.current);
|
|
87
|
+
|
|
88
|
+
return () => {
|
|
89
|
+
observer.disconnect();
|
|
90
|
+
if (timeoutId !== null) {
|
|
91
|
+
clearTimeout(timeoutId);
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
}, [ref, updateSize, delay]);
|
|
96
|
+
|
|
97
|
+
return [size, ref];
|
|
98
|
+
}
|