@redocly/theme 0.58.1 → 0.59.0-next.1

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 (104) hide show
  1. package/lib/components/Accordion/Accordion.d.ts +12 -0
  2. package/lib/components/Accordion/Accordion.js +75 -0
  3. package/lib/components/Accordion/AccordionBody.d.ts +8 -0
  4. package/lib/components/Accordion/AccordionBody.js +63 -0
  5. package/lib/components/Accordion/AccordionHeader.d.ts +10 -0
  6. package/lib/components/Accordion/AccordionHeader.js +37 -0
  7. package/lib/components/Accordion/AccordionTitle.d.ts +6 -0
  8. package/lib/components/Accordion/AccordionTitle.js +20 -0
  9. package/lib/components/Accordion/variables.d.ts +1 -0
  10. package/lib/components/Accordion/variables.js +59 -0
  11. package/lib/components/Buttons/AIAssistantButton.d.ts +2 -0
  12. package/lib/components/Buttons/AIAssistantButton.js +125 -0
  13. package/lib/components/Buttons/variables.d.ts +1 -0
  14. package/lib/components/Buttons/variables.dark.d.ts +1 -0
  15. package/lib/components/Buttons/variables.dark.js +10 -0
  16. package/lib/components/Buttons/variables.js +51 -0
  17. package/lib/components/Catalog/Catalog.js +3 -3
  18. package/lib/components/Catalog/CatalogEntity/CatalogEntityRelations/CatalogEntityApiDescriptionRelations.js +1 -1
  19. package/lib/components/Catalog/CatalogEntity/CatalogEntityRelations/CatalogEntityTeamRelations.js +1 -1
  20. package/lib/components/Catalog/CatalogFilter/CatalogFilter.d.ts +6 -0
  21. package/lib/components/Catalog/CatalogFilter/CatalogFilter.js +35 -0
  22. package/lib/components/Catalog/CatalogFilter/CatalogFilterCheckboxes.d.ts +6 -0
  23. package/lib/components/Catalog/CatalogFilter/CatalogFilterCheckboxes.js +142 -0
  24. package/lib/components/Catalog/CatalogFilter/CatalogFilterContent.d.ts +13 -0
  25. package/lib/components/Catalog/CatalogFilter/CatalogFilterContent.js +92 -0
  26. package/lib/components/Catalog/CatalogFilter/CatalogFilterDateRange.d.ts +6 -0
  27. package/lib/components/Catalog/CatalogFilter/CatalogFilterDateRange.js +111 -0
  28. package/lib/components/Catalog/CatalogFilter/CatalogFilterSelect.d.ts +6 -0
  29. package/lib/components/Catalog/CatalogFilter/CatalogFilterSelect.js +116 -0
  30. package/lib/components/Catalog/CatalogSelector.js +0 -1
  31. package/lib/components/Catalog/variables.js +0 -1
  32. package/lib/components/Filter/FilterInput.d.ts +1 -0
  33. package/lib/components/Filter/FilterInput.js +2 -2
  34. package/lib/components/Filter/FilterOptions.js +2 -0
  35. package/lib/components/Filter/variables.js +7 -4
  36. package/lib/components/Search/SearchAiDialog.js +2 -3
  37. package/lib/components/Search/SearchAiResponse.js +2 -3
  38. package/lib/components/Search/SearchDialog.d.ts +2 -1
  39. package/lib/components/Search/SearchDialog.js +2 -2
  40. package/lib/components/Tag/Tag.d.ts +3 -2
  41. package/lib/components/Tag/Tag.js +21 -5
  42. package/lib/components/Tag/variables.dark.js +135 -0
  43. package/lib/components/Tag/variables.js +120 -58
  44. package/lib/core/hooks/use-tabs.d.ts +11 -6
  45. package/lib/core/hooks/use-tabs.js +117 -207
  46. package/lib/core/styles/dark.js +29 -26
  47. package/lib/core/styles/global.js +64 -59
  48. package/lib/core/types/l10n.d.ts +1 -1
  49. package/lib/core/utils/index.d.ts +1 -0
  50. package/lib/core/utils/index.js +1 -0
  51. package/lib/core/utils/tabs.d.ts +1 -0
  52. package/lib/core/utils/tabs.js +8 -0
  53. package/lib/icons/RedoclyIcon/RedoclyIcon.d.ts +9 -0
  54. package/lib/icons/RedoclyIcon/RedoclyIcon.js +27 -0
  55. package/lib/index.d.ts +2 -0
  56. package/lib/index.js +2 -0
  57. package/lib/layouts/RootLayout.js +6 -1
  58. package/lib/markdoc/components/Tabs/Tab.js +1 -1
  59. package/lib/markdoc/components/Tabs/TabList.d.ts +2 -14
  60. package/lib/markdoc/components/Tabs/TabList.js +63 -16
  61. package/lib/markdoc/components/Tabs/Tabs.d.ts +2 -2
  62. package/lib/markdoc/components/Tabs/Tabs.js +11 -87
  63. package/lib/markdoc/tags/tabs.js +5 -0
  64. package/package.json +2 -2
  65. package/src/components/Accordion/Accordion.tsx +100 -0
  66. package/src/components/Accordion/AccordionBody.tsx +65 -0
  67. package/src/components/Accordion/AccordionHeader.tsx +68 -0
  68. package/src/components/Accordion/AccordionTitle.tsx +26 -0
  69. package/src/components/Accordion/variables.ts +56 -0
  70. package/src/components/Buttons/AIAssistantButton.tsx +141 -0
  71. package/src/components/Buttons/variables.dark.ts +7 -0
  72. package/src/components/Buttons/variables.ts +48 -0
  73. package/src/components/Catalog/Catalog.tsx +3 -2
  74. package/src/components/Catalog/CatalogEntity/CatalogEntityRelations/CatalogEntityApiDescriptionRelations.tsx +1 -1
  75. package/src/components/Catalog/CatalogEntity/CatalogEntityRelations/CatalogEntityTeamRelations.tsx +1 -1
  76. package/src/components/Catalog/CatalogFilter/CatalogFilter.tsx +56 -0
  77. package/src/components/Catalog/CatalogFilter/CatalogFilterCheckboxes.tsx +169 -0
  78. package/src/components/Catalog/CatalogFilter/CatalogFilterContent.tsx +121 -0
  79. package/src/components/Catalog/CatalogFilter/CatalogFilterDateRange.tsx +147 -0
  80. package/src/components/Catalog/CatalogFilter/CatalogFilterSelect.tsx +136 -0
  81. package/src/components/Catalog/CatalogSelector.tsx +0 -1
  82. package/src/components/Catalog/variables.ts +0 -1
  83. package/src/components/Filter/FilterInput.tsx +3 -2
  84. package/src/components/Filter/FilterOptions.tsx +2 -0
  85. package/src/components/Filter/variables.ts +7 -4
  86. package/src/components/Search/SearchAiDialog.tsx +2 -2
  87. package/src/components/Search/SearchAiResponse.tsx +2 -2
  88. package/src/components/Search/SearchDialog.tsx +7 -2
  89. package/src/components/Tag/Tag.tsx +33 -8
  90. package/src/components/Tag/variables.dark.ts +135 -0
  91. package/src/components/Tag/variables.ts +120 -58
  92. package/src/core/hooks/use-tabs.ts +160 -238
  93. package/src/core/styles/dark.ts +11 -8
  94. package/src/core/styles/global.ts +7 -2
  95. package/src/core/types/l10n.ts +1 -0
  96. package/src/core/utils/index.ts +1 -0
  97. package/src/core/utils/tabs.ts +4 -0
  98. package/src/icons/RedoclyIcon/RedoclyIcon.tsx +44 -0
  99. package/src/index.ts +2 -0
  100. package/src/layouts/RootLayout.tsx +6 -0
  101. package/src/markdoc/components/Tabs/Tab.tsx +1 -0
  102. package/src/markdoc/components/Tabs/TabList.tsx +84 -30
  103. package/src/markdoc/components/Tabs/Tabs.tsx +12 -125
  104. package/src/markdoc/tags/tabs.ts +5 -0
@@ -0,0 +1,65 @@
1
+ import React from 'react';
2
+ import styled, { keyframes, css } from 'styled-components';
3
+
4
+ import type { JSX, ReactNode } from 'react';
5
+
6
+ type AccordionBodyWrapperProps = {
7
+ $animate?: boolean;
8
+ $hidden?: boolean;
9
+ };
10
+
11
+ export type AccordionBodyProps = {
12
+ animate?: boolean;
13
+ hidden?: boolean;
14
+ children?: ReactNode;
15
+ className?: string;
16
+ };
17
+
18
+ export function AccordionBody({
19
+ animate,
20
+ hidden,
21
+ children,
22
+ className,
23
+ }: AccordionBodyProps): JSX.Element {
24
+ return (
25
+ <AccordionBodyWrapper
26
+ className={className}
27
+ $animate={animate}
28
+ $hidden={hidden}
29
+ data-component-name="Accordion/AccordionBody"
30
+ >
31
+ {children}
32
+ </AccordionBodyWrapper>
33
+ );
34
+ }
35
+
36
+ const showAccordion = keyframes`
37
+ 0% {
38
+ opacity: 0;
39
+ transform: translateY(-5px);
40
+ }
41
+ 100% {
42
+ opacity: 1;
43
+ transform: translateY(0);
44
+ }
45
+ `;
46
+
47
+ const showAccordionAnimation = css`
48
+ animation: ${showAccordion} 0.15s ease-out;
49
+ `;
50
+
51
+ const AccordionBodyWrapper = styled.div<AccordionBodyWrapperProps>`
52
+ ${({ $animate }) => $animate && showAccordionAnimation};
53
+ ${({ $hidden }) => $hidden && 'visibility: hidden'};
54
+
55
+ background-color: var(--accordion-body-bg-color);
56
+
57
+ font-family: var(--accordion-body-font-family);
58
+ font-size: var(--accordion-body-font-size);
59
+ font-weight: var(--accordion-body-font-weight);
60
+ border: var(--accordion-body-border);
61
+ padding: var(--accordion-body-padding);
62
+ color: var(--accordion-body-text-color);
63
+
64
+ border-radius: var(--accordion-border-radius);
65
+ `;
@@ -0,0 +1,68 @@
1
+ import React from 'react';
2
+ import styled from 'styled-components';
3
+
4
+ import type { BaseSyntheticEvent, JSX, ReactNode } from 'react';
5
+
6
+ export type AccordionHeaderProps = {
7
+ isExpandable?: boolean;
8
+ expanded?: boolean;
9
+ disabled?: boolean;
10
+ children?: ReactNode;
11
+ className?: string;
12
+ onClick?: (e: BaseSyntheticEvent) => void;
13
+ };
14
+
15
+ type AccordionHeaderWrapperProps = {
16
+ $isExpandable?: boolean;
17
+ $expanded?: boolean;
18
+ $disabled?: boolean;
19
+ };
20
+
21
+ export function AccordionHeader({
22
+ isExpandable,
23
+ expanded,
24
+ disabled,
25
+ children,
26
+ className,
27
+ onClick,
28
+ }: AccordionHeaderProps): JSX.Element {
29
+ return (
30
+ <AccordionHeaderWrapper
31
+ className={className}
32
+ $isExpandable={isExpandable}
33
+ $expanded={expanded}
34
+ $disabled={disabled}
35
+ onClick={onClick}
36
+ data-component-name="Accordion/AccordionHeader"
37
+ >
38
+ {children}
39
+ </AccordionHeaderWrapper>
40
+ );
41
+ }
42
+
43
+ const AccordionHeaderWrapper = styled.div<AccordionHeaderWrapperProps>`
44
+ display: flex;
45
+ gap: var(--spacing-xs);
46
+ align-items: center;
47
+ justify-content: space-between;
48
+ min-height: 32px;
49
+ color: var(--accordion-header-text-color);
50
+
51
+ cursor: ${({ $isExpandable = true }) => ($isExpandable ? 'pointer' : 'default')};
52
+
53
+ line-height: var(--accordion-header-line-height);
54
+ font-size: var(--accordion-header-font-size);
55
+ font-family: var(--accordion-header-font-family);
56
+ font-weight: var(--accordion-header-font-weight);
57
+ padding: var(--accordion-header-padding);
58
+ border: var(--accordion-header-border);
59
+ background-color: var(--accordion-header-bg-color);
60
+ border-radius: var(--accordion-border-radius);
61
+
62
+ pointer-events: ${({ $disabled }) => ($disabled ? 'none' : 'auto')};
63
+
64
+ &:hover {
65
+ color: ${({ $isExpandable = true }) =>
66
+ $isExpandable ? 'var(--text-color-primary)' : 'var(--accordion-header-text-color)'};
67
+ }
68
+ `;
@@ -0,0 +1,26 @@
1
+ import React from 'react';
2
+ import styled from 'styled-components';
3
+
4
+ import type { JSX, ReactNode } from 'react';
5
+
6
+ export type AccordionTitleProps = {
7
+ children?: ReactNode;
8
+ className?: string;
9
+ };
10
+
11
+ export function AccordionTitle({ children, className }: AccordionTitleProps): JSX.Element {
12
+ return (
13
+ <AccordionTitleWrapper className={className} data-component-name="Accordion/AccordionTitle">
14
+ {children}
15
+ </AccordionTitleWrapper>
16
+ );
17
+ }
18
+
19
+ const AccordionTitleWrapper = styled.span`
20
+ font-weight: var(--accordion-header-font-weight);
21
+ line-height: var(--line-height-base);
22
+ overflow: hidden;
23
+ text-overflow: ellipsis;
24
+ color: var(--accordion-header-text-color);
25
+ flex: 1;
26
+ `;
@@ -0,0 +1,56 @@
1
+ import { css } from 'styled-components';
2
+
3
+ export const accordion = css`
4
+ /**
5
+ * @tokens Accordion spacing
6
+ * @presenter Spacing
7
+ */
8
+
9
+ --accordion-gap: var(--spacing-base);
10
+
11
+ /**
12
+ * @tokens Accordion common
13
+ */
14
+
15
+ --accordion-border-radius: var(--border-radius); // @presenter BorderRadius
16
+ --accordion-border: none; // @presenter Border
17
+
18
+ --accordion-line-height: var(--line-height-base); // @presenter LineHeight
19
+ --accordion-font-size: var(--font-size-base); // @presenter FontSize
20
+ --accordion-font-family: var(--font-family-base); // @presenter FontFamily
21
+ --accordion-font-weight: var(--font-weight-regular); // @presenter FontWeight
22
+
23
+ /**
24
+ * @tokens Accordion header common
25
+ */
26
+
27
+ --accordion-header-font-family: var(--font-family-base); // @presenter FontFamily
28
+ --accordion-header-font-size: var(--font-size-base); // @presenter FontSize
29
+ --accordion-header-font-weight: var(--font-weight-medium, 500); // @presenter FontWeight
30
+ --accordion-header-text-color: var(--text-color-primary);
31
+ --accordion-header-padding: var(--spacing-sm) var(--spacing-base);
32
+ --accordion-header-line-height: var(--line-height-base); // @presenter LineHeight
33
+ --accordion-header-bg-color: transparent; // @presenter Color
34
+ --accordion-header-border: none; // @presenter Border
35
+
36
+ /**
37
+ * @tokens Accordion body common
38
+ */
39
+
40
+ --accordion-body-font-family: var(--font-family-base); // @presenter FontFamily
41
+ --accordion-body-font-size: var(--font-size-base); // @presenter FontSize
42
+ --accordion-body-font-weight: var(--font-weight-regular); // @presenter FontWeight
43
+ --accordion-body-text-color: var(--text-color-secondary);
44
+ --accordion-body-padding: 0 var(--spacing-xs) var(--spacing-sm);
45
+ --accordion-body-bg-color: transparent; // @presenter Color
46
+ --accordion-body-border: none; // @presenter Border
47
+
48
+ /**
49
+ * @tokens Accordion chevron icon
50
+ */
51
+
52
+ --accordion-chevron-icon-color: var(--text-color-secondary); // @presenter Color
53
+ --accordion-chevron-icon-size: 16px;
54
+
55
+ // @tokens End
56
+ `;
@@ -0,0 +1,141 @@
1
+ import * as React from 'react';
2
+ import { useState } from 'react';
3
+ import styled from 'styled-components';
4
+
5
+ import { Button } from '@redocly/theme/components/Button/Button';
6
+ import { SearchDialog } from '@redocly/theme/components/Search/SearchDialog';
7
+ import { useThemeConfig, useThemeHooks } from '@redocly/theme/core/hooks';
8
+ import { ChatIcon } from '@redocly/theme/icons/ChatIcon/ChatIcon';
9
+ import { AiStarsGradientIcon } from '@redocly/theme/icons/AiStarsGradientIcon/AiStarsGradientIcon';
10
+ import { RedoclyIcon } from '@redocly/theme/icons/RedoclyIcon/RedoclyIcon';
11
+
12
+ type AIAssistantButtonIconType = 'chat' | 'sparkles' | 'redocly';
13
+ type AIAssistantButtonType = 'button' | 'icon';
14
+
15
+ interface AIAssistantButtonConfig {
16
+ hide?: boolean;
17
+ inputType?: AIAssistantButtonType;
18
+ inputIcon?: AIAssistantButtonIconType;
19
+ }
20
+
21
+ const defaultConfig: Required<AIAssistantButtonConfig> = {
22
+ hide: false,
23
+ inputType: 'button',
24
+ inputIcon: 'redocly',
25
+ };
26
+
27
+ const getIcon = (
28
+ iconType: AIAssistantButtonIconType,
29
+ inputType: AIAssistantButtonType = 'button',
30
+ ) => {
31
+ const iconSize =
32
+ inputType === 'icon'
33
+ ? 'var(--ai-assistant-button-icon-icon-size)'
34
+ : 'var(--ai-assistant-button-text-icon-size)';
35
+
36
+ const redoclyIcon = (
37
+ <RedoclyIcon size={iconSize} color="var(--ai-assistant-button-redocly-icon-color)" />
38
+ );
39
+ const sparklesIcon = (
40
+ <AiStarsGradientIcon size={iconSize} color="var(--search-ai-button-icon-color)" />
41
+ );
42
+ const chatIcon = <ChatIcon size={iconSize} />;
43
+
44
+ switch (iconType) {
45
+ case 'chat':
46
+ return chatIcon;
47
+ case 'sparkles':
48
+ return sparklesIcon;
49
+ case 'redocly':
50
+ return redoclyIcon;
51
+ default:
52
+ return redoclyIcon;
53
+ }
54
+ };
55
+
56
+ export function AIAssistantButton() {
57
+ const [isOpen, setIsOpen] = useState(false);
58
+ const themeConfig = useThemeConfig();
59
+ const { useTranslate } = useThemeHooks();
60
+ const { translate } = useTranslate();
61
+
62
+ const buttonConfig = {
63
+ ...defaultConfig,
64
+ ...(themeConfig.aiAssistant?.trigger ?? {}),
65
+ };
66
+
67
+ const { hide, inputIcon, inputType } = buttonConfig;
68
+
69
+ if (hide) {
70
+ return null;
71
+ }
72
+
73
+ const icon = getIcon(inputIcon, inputType);
74
+ const text = translate('aiAssistant.trigger', 'Ask AI');
75
+
76
+ const handleOpen = () => {
77
+ setIsOpen(true);
78
+ };
79
+
80
+ const handleClose = () => {
81
+ setIsOpen(false);
82
+ };
83
+
84
+ return (
85
+ <>
86
+ <StyledAIAssistantButton
87
+ variant="outlined"
88
+ size="medium"
89
+ $inputType={inputType}
90
+ onClick={handleOpen}
91
+ aria-label={`AI Assistant button - ${inputIcon}`}
92
+ data-component-name="Buttons/AIAssistantButton"
93
+ >
94
+ {icon}
95
+ {inputType === 'button' && text}
96
+ </StyledAIAssistantButton>
97
+
98
+ {isOpen && <SearchDialog onClose={handleClose} initialMode="ai-dialog" />}
99
+ </>
100
+ );
101
+ }
102
+
103
+ const StyledAIAssistantButton = styled(Button)<{ $inputType?: AIAssistantButtonType }>`
104
+ position: fixed;
105
+ bottom: var(--ai-assistant-button-bottom);
106
+ right: var(--ai-assistant-button-right);
107
+ ${({ $inputType }) =>
108
+ $inputType === 'icon'
109
+ ? `
110
+ border-radius: var(--ai-assistant-button-border-radius-icon);
111
+ width: var(--ai-assistant-button-icon-size);
112
+ height: var(--ai-assistant-button-icon-size);
113
+ `
114
+ : `
115
+ border-radius: var(--ai-assistant-button-border-radius-text);
116
+ padding: var(--ai-assistant-button-text-padding);
117
+ height: var(--ai-assistant-button-text-height);
118
+ `}
119
+ min-width: var(--ai-assistant-button-min-width);
120
+ font-size: var(--ai-assistant-button-font-size);
121
+ font-weight: var(--ai-assistant-button-font-weight);
122
+ box-shadow: var(--bg-raised-shadow);
123
+ z-index: var(--ai-assistant-button-z-index);
124
+ display: flex;
125
+ align-items: center;
126
+ justify-content: center;
127
+ gap: var(--ai-assistant-button-gap);
128
+ background-color: var(--ai-assistant-button-bg-color) !important;
129
+
130
+ transition: var(--ai-assistant-button-transition);
131
+
132
+ &:hover {
133
+ box-shadow: var(--ai-assistant-button-shadow-hover);
134
+ transform: var(--ai-assistant-button-transform-hover);
135
+ }
136
+
137
+ &:active {
138
+ transform: var(--bg-raised-shadow);
139
+ box-shadow: var(--ai-assistant-button-shadow-active);
140
+ }
141
+ `;
@@ -0,0 +1,7 @@
1
+ import { css } from 'styled-components';
2
+
3
+ export const aiAssistantButtonDarkMode = css`
4
+
5
+ /* Background color */
6
+ --ai-assistant-button-bg-color: var(--color-warm-grey-4);
7
+ `;
@@ -0,0 +1,48 @@
1
+ import { css } from 'styled-components';
2
+
3
+ export const aiAssistantButton = css`
4
+ /**
5
+ * @tokens AI Assistant Button
6
+ * @presenter Color
7
+ */
8
+
9
+ /* Button sizing */
10
+ --ai-assistant-button-icon-size: 40px;
11
+ --ai-assistant-button-text-height: 40px;
12
+ --ai-assistant-button-text-padding: var(--spacing-sm) var(--spacing-md);
13
+ --ai-assistant-button-border-radius-icon: 50%;
14
+ --ai-assistant-button-border-radius-text: 1.75rem;
15
+ --ai-assistant-button-min-width: auto;
16
+
17
+ /* Icon sizing */
18
+ --ai-assistant-button-icon-icon-size: 18px;
19
+ --ai-assistant-button-text-icon-size: 20px;
20
+
21
+ /* Positioning */
22
+ --ai-assistant-button-bottom: var(--spacing-xl);
23
+ --ai-assistant-button-right: var(--spacing-xl);
24
+ --ai-assistant-button-z-index: 1000;
25
+
26
+ /* Typography */
27
+ --ai-assistant-button-font-size: var(--font-size-base);
28
+ --ai-assistant-button-font-weight: var(--font-weight-medium);
29
+ --ai-assistant-button-gap: var(--spacing-xs);
30
+
31
+ /* Background color */
32
+ --ai-assistant-button-bg-color: var(--color-static-white);
33
+
34
+ /* Icon colors */
35
+ --ai-assistant-button-redocly-icon-color: #297AFE;
36
+
37
+ /* Transform */
38
+ --ai-assistant-button-transform-hover: none;
39
+ --ai-assistant-button-transform-active: translateY(0);
40
+
41
+ /* Shadow */
42
+ --ai-assistant-button-shadow-hover:
43
+ 2px 1px 12px 4px rgba(143, 98, 254, 0.2),
44
+ -3px -2px 24px 0px rgba(41, 122, 254, 0.2);
45
+
46
+ /* Transition */
47
+ --ai-assistant-button-transition: box-shadow 0.3s ease, transform 0.2s ease;
48
+ `;
@@ -10,7 +10,7 @@ import {
10
10
  import { breakpoints } from '@redocly/theme/core/utils';
11
11
  import { useThemeHooks } from '@redocly/theme/core/hooks';
12
12
  import { H3 } from '@redocly/theme/components/Typography/H3';
13
- import { FilterContent } from '@redocly/theme/components/Filter/FilterContent';
13
+ import { CatalogFilterContent } from '@redocly/theme/components/Catalog/CatalogFilter/CatalogFilterContent';
14
14
  import { Sidebar, SidebarHeader } from '@redocly/theme/components/Sidebar/Sidebar';
15
15
  import { CatalogSelector } from '@redocly/theme/components/Catalog/CatalogSelector';
16
16
  import { SidebarActions } from '@redocly/theme/components/SidebarActions/SidebarActions';
@@ -89,7 +89,7 @@ export function Catalog(props: CatalogProps): JSX.Element {
89
89
  )
90
90
  }
91
91
  menu={
92
- <FilterContent
92
+ <CatalogFilterContent
93
93
  setFilterTerm={setSearchQuery}
94
94
  filters={filters}
95
95
  filterTerm={searchQuery}
@@ -128,6 +128,7 @@ export function Catalog(props: CatalogProps): JSX.Element {
128
128
  <FilterInput
129
129
  value={searchQuery}
130
130
  onChange={(updatedTerm) => setSearchQuery(updatedTerm)}
131
+ dataTestId="catalog-search-input"
131
132
  />
132
133
  </CatalogSearchInputWrapper>
133
134
 
@@ -46,7 +46,7 @@ export function CatalogEntityApiDescriptionRelations({
46
46
  }: CatalogEntityApiDescriptionRelationsProps): JSX.Element {
47
47
  return (
48
48
  <div data-component-name="Catalog/CatalogEntity/CatalogEntityRelations/CatalogEntityApiDescriptionRelations">
49
- <Tabs key={entity.id} forceReady={relations.length > 0} size={TabsSize.MEDIUM}>
49
+ <Tabs key={entity.id} size={TabsSize.MEDIUM}>
50
50
  <TabItem
51
51
  label="Operations"
52
52
  icon={<MoleculesIcon />}
@@ -74,7 +74,7 @@ export function CatalogEntityTeamRelations({
74
74
  }: CatalogEntityTeamRelationsProps): JSX.Element {
75
75
  return (
76
76
  <div data-component-name="Catalog/CatalogEntity/CatalogEntityRelations/CatalogEntityTeamRelations">
77
- <Tabs forceReady={relations.length > 0} size={TabsSize.MEDIUM}>
77
+ <Tabs size={TabsSize.MEDIUM}>
78
78
  <TabItem label="Members" icon={<PeopleIcon />} onClick={() => setFilter('type:user')}>
79
79
  <CatalogEntityRelationsTable
80
80
  key="members-table"
@@ -0,0 +1,56 @@
1
+ import React from 'react';
2
+ import styled from 'styled-components';
3
+
4
+ import type { JSX } from 'react';
5
+ import type { FilterProps, FilterTypes } from '@redocly/theme/core/types';
6
+
7
+ import { CatalogFilterSelect } from '@redocly/theme/components/Catalog/CatalogFilter/CatalogFilterSelect';
8
+ import { CatalogFilterCheckboxes } from '@redocly/theme/components/Catalog/CatalogFilter/CatalogFilterCheckboxes';
9
+ import { CatalogFilterDateRange } from '@redocly/theme/components/Catalog/CatalogFilter/CatalogFilterDateRange';
10
+
11
+ const filterComponents = {
12
+ select: CatalogFilterSelect,
13
+ 'date-range': CatalogFilterDateRange,
14
+ checkboxes: CatalogFilterCheckboxes,
15
+ } as const;
16
+
17
+ export type CatalogFilterProps = FilterProps & {
18
+ className?: string;
19
+ };
20
+
21
+ export function CatalogFilter({
22
+ filter,
23
+ filterValuesCasing,
24
+ showCounter = true,
25
+ className,
26
+ }: CatalogFilterProps): JSX.Element | null {
27
+ if (!filter.parentUsed) return null;
28
+
29
+ const FilterComponent = filterComponents[(filter.type || 'checkboxes') as FilterTypes];
30
+
31
+ return (
32
+ <CatalogFilterGroup
33
+ className={className}
34
+ data-component-name="Catalog/CatalogFilter"
35
+ key={filter.property + filter.title}
36
+ >
37
+ <FilterComponent
38
+ filter={filter}
39
+ filterValuesCasing={filterValuesCasing}
40
+ showCounter={showCounter}
41
+ />
42
+ </CatalogFilterGroup>
43
+ );
44
+ }
45
+
46
+ const CatalogFilterGroup = styled.div`
47
+ padding: var(--filter-group-padding);
48
+ display: flex;
49
+ flex-direction: column;
50
+ gap: var(--filter-group-gap);
51
+ border-bottom: 1px solid var(--catalog-table-border-color);
52
+
53
+ &:first-child {
54
+ border-top: 1px solid var(--catalog-table-border-color);
55
+ }
56
+ `;
@@ -0,0 +1,169 @@
1
+ import React, { useState, useMemo } from 'react';
2
+ import styled from 'styled-components';
3
+ import { findAll } from 'highlight-words-core';
4
+
5
+ import type { JSX } from 'react';
6
+ import type { FilterProps } from '@redocly/theme/core/types';
7
+
8
+ import { FilterOptions } from '@redocly/theme/components/Filter/FilterOptions';
9
+ import { FilterOption } from '@redocly/theme/components/Filter/FilterOption';
10
+ import { FilterOptionLabel } from '@redocly/theme/components/Filter/FilterOptionLabel';
11
+ import { FilterInput } from '@redocly/theme/components/Filter/FilterInput';
12
+ import { useThemeHooks } from '@redocly/theme/core/hooks';
13
+ import { CheckboxIcon } from '@redocly/theme/icons/CheckboxIcon/CheckboxIcon';
14
+ import { CounterTag } from '@redocly/theme/components/Tags/CounterTag';
15
+ import { changeTextCasing } from '@redocly/theme/core/utils';
16
+ import { Accordion } from '@redocly/theme/components/Accordion/Accordion';
17
+
18
+ export type CatalogFilterCheckboxesProps = FilterProps & {
19
+ className?: string;
20
+ };
21
+
22
+ export function CatalogFilterCheckboxes({
23
+ filter,
24
+ filterValuesCasing,
25
+ showCounter = true,
26
+ className,
27
+ }: CatalogFilterCheckboxesProps): JSX.Element {
28
+ const { useTranslate, useTelemetry } = useThemeHooks();
29
+ const { translate } = useTranslate();
30
+ const telemetry = useTelemetry();
31
+
32
+ const [searchTerm, setSearchTerm] = useState('');
33
+ const [isExpanded, setIsExpanded] = useState(false);
34
+
35
+ const filteredOptions = useMemo(() => {
36
+ const options = filter.filteredOptions || filter.options;
37
+ if (!searchTerm.trim()) return options;
38
+
39
+ const lowerSearchTerm = searchTerm.toLowerCase();
40
+ return options.filter(({ value }) => {
41
+ const translatedValue = changeTextCasing(translate(value), filterValuesCasing);
42
+ if (!translatedValue) return false;
43
+ return translatedValue.toLowerCase().includes(lowerSearchTerm);
44
+ });
45
+ }, [searchTerm, filter.filteredOptions, filter.options, translate, filterValuesCasing]);
46
+
47
+ const selectedCount = useMemo(() => {
48
+ if (filter.selectedOptions instanceof Set) {
49
+ return filter.selectedOptions.size;
50
+ }
51
+ return 0;
52
+ }, [filter.selectedOptions]);
53
+
54
+ const highlightText = (text: string): JSX.Element => {
55
+ if (!searchTerm.trim()) {
56
+ return <>{text}</>;
57
+ }
58
+
59
+ const chunks = findAll({
60
+ searchWords: [searchTerm],
61
+ textToHighlight: text,
62
+ });
63
+
64
+ return (
65
+ <>
66
+ {chunks.map((chunk, idx) => {
67
+ const { end, highlight, start } = chunk;
68
+ const chunkText = text.substr(start, end - start);
69
+ if (highlight) {
70
+ return <HighlightedText key={idx}>{chunkText}</HighlightedText>;
71
+ } else {
72
+ return <span key={idx}>{chunkText}</span>;
73
+ }
74
+ })}
75
+ </>
76
+ );
77
+ };
78
+
79
+ const headerContent = (
80
+ <AccordionHeaderContent>
81
+ <span>{translate(filter.titleTranslationKey, filter.title)}</span>
82
+ {selectedCount > 0 && <CounterTag borderless>{selectedCount}</CounterTag>}
83
+ </AccordionHeaderContent>
84
+ );
85
+
86
+ return (
87
+ <AccordionWrapper
88
+ className={className}
89
+ $hasSelection={selectedCount > 0 && !isExpanded}
90
+ data-component-name="Catalog/CatalogFilterCheckboxes"
91
+ >
92
+ <Accordion expanded={isExpanded} header={headerContent} onToggle={setIsExpanded}>
93
+ <FilterSearchWrapper>
94
+ <FilterInput value={searchTerm} onChange={setSearchTerm} />
95
+ </FilterSearchWrapper>
96
+ <FilterOptions>
97
+ {filteredOptions.map(({ value, count }) => {
98
+ const id = 'filter--' + filter.property + '--' + value;
99
+ return (
100
+ <FilterCheckboxOption
101
+ key={id}
102
+ role="link"
103
+ onClick={() => {
104
+ filter.toggleOption(value);
105
+ telemetry.sendFilterCheckboxToggledMessage({ id });
106
+ }}
107
+ >
108
+ <CheckboxIcon
109
+ checked={
110
+ filter.selectedOptions instanceof Set
111
+ ? filter.selectedOptions.has(value) ||
112
+ filter.selectedOptions.has(value?.toLowerCase())
113
+ : false
114
+ }
115
+ />
116
+ <FilterOptionLabel data-translation-key={value}>
117
+ {highlightText(changeTextCasing(translate(value), filterValuesCasing) || '')}
118
+ </FilterOptionLabel>
119
+ {showCounter && <CounterTag borderless>{count}</CounterTag>}
120
+ </FilterCheckboxOption>
121
+ );
122
+ })}
123
+ </FilterOptions>
124
+ </Accordion>
125
+ </AccordionWrapper>
126
+ );
127
+ }
128
+
129
+ const AccordionWrapper = styled.div<{ $hasSelection: boolean }>`
130
+ position: relative;
131
+ border-right: 4px solid transparent;
132
+
133
+ ${({ $hasSelection }) =>
134
+ $hasSelection &&
135
+ `
136
+ border-right-color: var(--color-blueberry-6);
137
+
138
+ &::after {
139
+ content: '';
140
+ position: absolute;
141
+ bottom: -1px;
142
+ right: -4px;
143
+ width: 4px;
144
+ height: 1px;
145
+ background-color: var(--color-blueberry-6);
146
+ z-index: 1;
147
+ }
148
+ `}
149
+ `;
150
+
151
+ const AccordionHeaderContent = styled.div`
152
+ display: flex;
153
+ align-items: center;
154
+ gap: var(--spacing-xs);
155
+ width: 100%;
156
+ `;
157
+
158
+ const HighlightedText = styled.span`
159
+ background-color: var(--catalog-highlight-bg-color);
160
+ color: var(--catalog-highlight-text-color);
161
+ `;
162
+
163
+ const FilterCheckboxOption = styled(FilterOption)`
164
+ padding-left: var(--filter-option-checkbox-padding-left);
165
+ `;
166
+
167
+ const FilterSearchWrapper = styled.div`
168
+ padding: var(--filter-content-search-padding);
169
+ `;