@redocly/theme 0.52.0-next.2 → 0.52.0-next.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/lib/components/Button/Button.d.ts +1 -0
  2. package/lib/components/Button/Button.js +2 -1
  3. package/lib/components/Catalog/Catalog.js +2 -26
  4. package/lib/components/Catalog/CatalogCard.js +1 -0
  5. package/lib/components/Catalog/CatalogVirtualizedGroups.d.ts +25 -0
  6. package/lib/components/Catalog/CatalogVirtualizedGroups.js +183 -0
  7. package/lib/components/Catalog/variables.js +3 -0
  8. package/lib/components/Dropdown/Dropdown.js +1 -1
  9. package/lib/components/Link/Link.js +2 -2
  10. package/lib/components/Menu/MenuItem.js +1 -1
  11. package/lib/components/Navbar/NavbarItem.js +4 -1
  12. package/lib/components/Search/FilterFields/SearchFilterFieldTags.js +12 -5
  13. package/lib/components/Search/SearchAiMessage.js +6 -5
  14. package/lib/components/Search/SearchDialog.js +79 -28
  15. package/lib/components/Search/SearchInput.d.ts +2 -2
  16. package/lib/components/Search/SearchInput.js +4 -4
  17. package/lib/components/Search/SearchItem.d.ts +3 -1
  18. package/lib/components/Search/SearchItem.js +53 -28
  19. package/lib/components/Search/variables.js +0 -1
  20. package/lib/components/Select/Select.js +6 -3
  21. package/lib/components/Select/SelectInput.d.ts +1 -0
  22. package/lib/components/Select/SelectInput.js +13 -2
  23. package/lib/components/Tag/Tag.d.ts +3 -1
  24. package/lib/components/Tag/Tag.js +2 -2
  25. package/lib/components/UserMenu/UserMenu.js +1 -1
  26. package/lib/core/hooks/__mocks__/index.d.ts +1 -0
  27. package/lib/core/hooks/__mocks__/index.js +1 -0
  28. package/lib/core/hooks/__mocks__/use-element-size.d.ts +4 -0
  29. package/lib/core/hooks/__mocks__/use-element-size.js +9 -0
  30. package/lib/core/hooks/index.d.ts +1 -0
  31. package/lib/core/hooks/index.js +1 -0
  32. package/lib/core/hooks/menu/use-collapse.js +0 -1
  33. package/lib/core/hooks/menu/use-nested-menu.js +5 -0
  34. package/lib/core/hooks/use-dialog-hotkeys.js +1 -2
  35. package/lib/core/hooks/use-element-size.d.ts +13 -0
  36. package/lib/core/hooks/use-element-size.js +75 -0
  37. package/lib/core/styles/global.js +0 -1
  38. package/lib/core/types/l10n.d.ts +1 -1
  39. package/lib/icons/CheckboxFilledIcon/CheckboxFilledIcon.d.ts +9 -0
  40. package/lib/icons/CheckboxFilledIcon/CheckboxFilledIcon.js +22 -0
  41. package/lib/icons/ReturnKeyIcon/ReturnKeyIcon.d.ts +3 -0
  42. package/lib/icons/ReturnKeyIcon/ReturnKeyIcon.js +18 -0
  43. package/lib/icons/WarningAltFilled/WarningAltFilled.d.ts +9 -0
  44. package/lib/icons/WarningAltFilled/WarningAltFilled.js +23 -0
  45. package/lib/index.d.ts +3 -1
  46. package/lib/index.js +3 -1
  47. package/package.json +2 -2
  48. package/src/components/Button/Button.tsx +4 -1
  49. package/src/components/Catalog/Catalog.tsx +3 -37
  50. package/src/components/Catalog/CatalogCard.tsx +1 -0
  51. package/src/components/Catalog/CatalogVirtualizedGroups.tsx +236 -0
  52. package/src/components/Catalog/variables.ts +3 -0
  53. package/src/components/Dropdown/Dropdown.tsx +0 -1
  54. package/src/components/Link/Link.tsx +2 -1
  55. package/src/components/Menu/MenuItem.tsx +1 -0
  56. package/src/components/Navbar/NavbarItem.tsx +5 -1
  57. package/src/components/Search/FilterFields/SearchFilterFieldTags.tsx +13 -4
  58. package/src/components/Search/SearchAiMessage.tsx +4 -4
  59. package/src/components/Search/SearchDialog.tsx +127 -53
  60. package/src/components/Search/SearchInput.tsx +10 -3
  61. package/src/components/Search/SearchItem.tsx +89 -55
  62. package/src/components/Search/variables.ts +0 -1
  63. package/src/components/Select/Select.tsx +7 -2
  64. package/src/components/Select/SelectInput.tsx +14 -2
  65. package/src/components/Tag/Tag.tsx +6 -0
  66. package/src/components/UserMenu/UserMenu.tsx +1 -1
  67. package/src/core/hooks/__mocks__/index.ts +1 -0
  68. package/src/core/hooks/__mocks__/use-element-size.ts +6 -0
  69. package/src/core/hooks/index.ts +1 -0
  70. package/src/core/hooks/menu/use-collapse.ts +0 -1
  71. package/src/core/hooks/menu/use-nested-menu.ts +4 -0
  72. package/src/core/hooks/use-dialog-hotkeys.ts +1 -1
  73. package/src/core/hooks/use-element-size.ts +98 -0
  74. package/src/core/styles/global.ts +0 -1
  75. package/src/core/types/l10n.ts +2 -0
  76. package/src/icons/CheckboxFilledIcon/CheckboxFilledIcon.tsx +23 -0
  77. package/src/icons/ReturnKeyIcon/ReturnKeyIcon.tsx +13 -0
  78. package/src/icons/WarningAltFilled/WarningAltFilled.tsx +24 -0
  79. package/src/index.ts +3 -1
  80. package/lib/components/Footer/FooterLogo.d.ts +0 -3
  81. package/lib/components/Footer/FooterLogo.js +0 -22
  82. package/src/components/Footer/FooterLogo.tsx +0 -20
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@redocly/theme",
3
- "version": "0.52.0-next.2",
3
+ "version": "0.52.0-next.4",
4
4
  "description": "Shared UI components lib",
5
5
  "keywords": [
6
6
  "theme",
@@ -88,7 +88,7 @@
88
88
  "react-calendar": "4.2.1",
89
89
  "react-date-picker": "10.0.3",
90
90
  "timeago.js": "4.0.2",
91
- "@redocly/config": "0.22.1"
91
+ "@redocly/config": "0.22.2"
92
92
  },
93
93
  "scripts": {
94
94
  "watch": "tsc -p tsconfig.build.json && (concurrently \"tsc -w -p tsconfig.build.json\" \"tsc-alias -w -p tsconfig.build.json\")",
@@ -29,6 +29,7 @@ export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElemen
29
29
  icon?: JSX.Element;
30
30
  iconPosition?: 'left' | 'right';
31
31
  title?: string;
32
+ tabIndex?: number;
32
33
  onClick?: (e?: any) => void;
33
34
 
34
35
  type?: 'button' | 'submit' | 'reset';
@@ -170,12 +171,14 @@ const StyledButton = styled.button.attrs((props: ButtonProps) => ({
170
171
  `;
171
172
 
172
173
  const ButtonComponent: React.FC<ButtonProps> = (props) => {
174
+ const tabIndex = 'tabIndex' in props ? props.tabIndex : props.to ? -1 : undefined;
175
+
173
176
  const button = (
174
177
  <StyledButton
175
178
  data-component-name="Button/Button"
176
179
  {...props}
177
180
  iconOnly={!props.children && props.icon !== null}
178
- tabIndex={props.to ? -1 : undefined}
181
+ tabIndex={tabIndex}
179
182
  >
180
183
  {props.icon && props.iconPosition !== 'right' && props.icon}
181
184
  {props.children}
@@ -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 { CounterTag } from '@redocly/theme/components/Tags/CounterTag';
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
- {groups.map((group) => (
77
- <React.Fragment key={group.title}>
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
 
@@ -71,7 +71,6 @@ export function Dropdown({
71
71
  onClick: triggerEvent === 'click' ? handleToggle : undefined,
72
72
  icon: withArrow ? isOpen ? <ChevronUpIcon /> : <ChevronDownIcon /> : undefined,
73
73
  ...(withArrow ? { iconPosition: 'right' } : {}),
74
- tabIndex: 0,
75
74
  ...triggerChild.props,
76
75
  onKeyDown: triggerEvent === 'click' ? handleKeyDown : undefined,
77
76
  });
@@ -29,10 +29,11 @@ export function Link(props: React.PropsWithChildren<LinkProps>): JSX.Element {
29
29
  hasActiveSubItem: _2,
30
30
  routeSlug: _3,
31
31
  external: _4,
32
+ innerRef,
32
33
  ...filteredProps
33
34
  } = props;
34
35
 
35
36
  // We omit "active" property to avoid "Warning: Received `false` for a non-boolean attribute `active`."
36
- return <ReactLink ref={filteredProps.innerRef} {...filteredProps} />;
37
+ return <ReactLink ref={innerRef} {...filteredProps} />;
37
38
  }
38
39
  }
@@ -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
- {translate(item.labelTranslationKey, item.label)}
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
  });
@@ -26,15 +26,24 @@ export function SearchFilterFieldTags({
26
26
  {facet.values.map((facetCount, index) => {
27
27
  const { value, count, isCounterVisible } = facetCount as SearchFacetCount;
28
28
  const active = selectedValues.includes(value);
29
+ const updateSelectedValues = () => {
30
+ const values = active
31
+ ? selectedValues.filter((item) => item !== value)
32
+ : [...selectedValues, value];
33
+ onChange(values);
34
+ };
29
35
  return (
30
36
  <FilterTagWrapper
31
37
  key={`${count}-${index}`}
38
+ tabIndex={0}
32
39
  color={value}
33
40
  onClick={() => {
34
- const values = active
35
- ? selectedValues.filter((item) => item !== value)
36
- : [...selectedValues, value];
37
- onChange(values);
41
+ updateSelectedValues();
42
+ }}
43
+ onKeyDown={(e) => {
44
+ if (e.key === 'Enter') {
45
+ updateSelectedValues();
46
+ }
38
47
  }}
39
48
  active={active}
40
49
  borderless
@@ -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
- {resources.length} {translate('search.ai.resourcesFound', 'resources found')}
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
- text-overflow: ellipsis;
170
- white-space: nowrap;
169
+ word-break: break-word;
170
+ white-space: normal;
171
171
  max-width: 100%;
172
172
  }
173
173
  `;