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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (155) hide show
  1. package/lib/components/Catalog/Catalog.js +1 -1
  2. package/lib/components/Catalog/CatalogEntity/CatalogEntityHistory/CatalogEntityHistorySidebar.js +2 -2
  3. package/lib/components/Catalog/CatalogEntity/CatalogEntitySchema.js +1 -1
  4. package/lib/components/Catalog/CatalogMobileFiltersPanel/CatalogMobileFiltersPanel.js +1 -1
  5. package/lib/components/Catalog/CatalogMobileFiltersPanel/CatalogMobileFiltersPanelOverlay.js +2 -2
  6. package/lib/components/CodeBlock/CodeBlockControls.js +3 -5
  7. package/lib/components/Consent/Consent.d.ts +2 -0
  8. package/lib/components/Consent/Consent.js +17 -0
  9. package/lib/components/Consent/ConsentBanner.d.ts +2 -0
  10. package/lib/components/Consent/ConsentBanner.js +120 -0
  11. package/lib/components/Consent/ConsentDialog.d.ts +2 -0
  12. package/lib/components/Consent/ConsentDialog.js +128 -0
  13. package/lib/components/Consent/ConsentSettingsLink.d.ts +2 -0
  14. package/lib/components/Consent/ConsentSettingsLink.js +35 -0
  15. package/lib/components/Consent/variables.d.ts +1 -0
  16. package/lib/components/Consent/variables.js +31 -0
  17. package/lib/components/Dropdown/Dropdown.js +3 -1
  18. package/lib/components/Footer/Footer.js +4 -1
  19. package/lib/components/JsonViewer/Helpers.d.ts +10 -2
  20. package/lib/components/JsonViewer/Helpers.js +29 -33
  21. package/lib/components/JsonViewer/JsonViewer.js +15 -18
  22. package/lib/components/Markdown/Markdown.js +3 -2
  23. package/lib/components/Markdown/styles/base-table.js +14 -1
  24. package/lib/components/Markdown/variables.js +1 -0
  25. package/lib/components/Menu/Menu.js +4 -0
  26. package/lib/components/Menu/MenuSelect.d.ts +7 -0
  27. package/lib/components/Menu/MenuSelect.js +129 -0
  28. package/lib/components/Menu/variables.js +43 -4
  29. package/lib/components/Navbar/Navbar.js +44 -5
  30. package/lib/components/Navbar/variables.js +3 -0
  31. package/lib/components/NavigationTabs/NavigationTab.d.ts +7 -0
  32. package/lib/components/NavigationTabs/NavigationTab.js +93 -0
  33. package/lib/components/NavigationTabs/NavigationTabContent.d.ts +7 -0
  34. package/lib/components/NavigationTabs/NavigationTabContent.js +50 -0
  35. package/lib/components/NavigationTabs/NavigationTabMenu.d.ts +7 -0
  36. package/lib/components/NavigationTabs/NavigationTabMenu.js +18 -0
  37. package/lib/components/NavigationTabs/NavigationTabs.d.ts +7 -0
  38. package/lib/components/NavigationTabs/NavigationTabs.js +97 -0
  39. package/lib/components/NavigationTabs/variables.d.ts +1 -0
  40. package/lib/components/NavigationTabs/variables.js +37 -0
  41. package/lib/components/Product/utils.js +1 -1
  42. package/lib/components/Search/SearchAiMessage.js +1 -1
  43. package/lib/components/Search/SearchAiResponse.js +1 -0
  44. package/lib/components/Search/SearchTrigger.js +8 -2
  45. package/lib/components/Sidebar/Sidebar.js +2 -2
  46. package/lib/components/TableOfContent/TableOfContent.js +2 -2
  47. package/lib/core/constants/common.d.ts +1 -0
  48. package/lib/core/constants/common.js +1 -0
  49. package/lib/core/constants/consent.d.ts +2 -0
  50. package/lib/core/constants/consent.js +30 -0
  51. package/lib/core/constants/index.d.ts +1 -0
  52. package/lib/core/constants/index.js +1 -0
  53. package/lib/core/hooks/index.d.ts +3 -0
  54. package/lib/core/hooks/index.js +3 -0
  55. package/lib/core/hooks/menu/use-mobile-menu-items.js +75 -51
  56. package/lib/core/hooks/menu/use-mobile-menu-levels.js +12 -7
  57. package/lib/core/hooks/use-consent-dialog.d.ts +12 -0
  58. package/lib/core/hooks/use-consent-dialog.js +36 -0
  59. package/lib/core/hooks/use-consent-telemetry.d.ts +1 -0
  60. package/lib/core/hooks/use-consent-telemetry.js +42 -0
  61. package/lib/core/hooks/use-scroll-item-into-view.d.ts +2 -0
  62. package/lib/core/hooks/use-scroll-item-into-view.js +16 -0
  63. package/lib/core/hooks/use-theme-hooks.js +16 -0
  64. package/lib/core/styles/global.js +54 -42
  65. package/lib/core/types/consent.d.ts +54 -0
  66. package/lib/core/types/consent.js +3 -0
  67. package/lib/core/types/hooks.d.ts +7 -0
  68. package/lib/core/types/index.d.ts +2 -0
  69. package/lib/core/types/l10n.d.ts +1 -1
  70. package/lib/core/types/navigation-tabs.d.ts +49 -0
  71. package/lib/core/types/navigation-tabs.js +3 -0
  72. package/lib/core/utils/consent-settings-link-placement.d.ts +3 -0
  73. package/lib/core/utils/consent-settings-link-placement.js +15 -0
  74. package/lib/core/utils/index.d.ts +2 -0
  75. package/lib/core/utils/index.js +2 -0
  76. package/lib/core/utils/mcp.js +1 -1
  77. package/lib/core/utils/menu.d.ts +4 -0
  78. package/lib/core/utils/menu.js +48 -8
  79. package/lib/core/utils/navigation-tabs.d.ts +8 -0
  80. package/lib/core/utils/navigation-tabs.js +71 -0
  81. package/lib/core/utils/urls.js +3 -1
  82. package/lib/icons/ExpandCollapseIcon/ExpandCollapseIcon.d.ts +29 -0
  83. package/lib/icons/ExpandCollapseIcon/ExpandCollapseIcon.js +81 -0
  84. package/lib/index.d.ts +6 -0
  85. package/lib/index.js +8 -1
  86. package/lib/layouts/PageLayout.js +1 -1
  87. package/lib/layouts/RootLayout.js +2 -2
  88. package/lib/markdoc/components/CodeWalkthrough/CodeFilters.js +1 -1
  89. package/lib/markdoc/components/CodeWalkthrough/CodePanel.js +3 -3
  90. package/lib/markdoc/components/CodeWalkthrough/CodeWalkthrough.js +1 -1
  91. package/package.json +3 -3
  92. package/src/components/Catalog/Catalog.tsx +1 -1
  93. package/src/components/Catalog/CatalogEntity/CatalogEntityHistory/CatalogEntityHistorySidebar.tsx +2 -2
  94. package/src/components/Catalog/CatalogEntity/CatalogEntitySchema.tsx +1 -1
  95. package/src/components/Catalog/CatalogMobileFiltersPanel/CatalogMobileFiltersPanel.tsx +1 -1
  96. package/src/components/Catalog/CatalogMobileFiltersPanel/CatalogMobileFiltersPanelOverlay.tsx +2 -2
  97. package/src/components/CodeBlock/CodeBlockControls.tsx +4 -7
  98. package/src/components/Consent/Consent.tsx +18 -0
  99. package/src/components/Consent/ConsentBanner.tsx +110 -0
  100. package/src/components/Consent/ConsentDialog.tsx +210 -0
  101. package/src/components/Consent/ConsentSettingsLink.tsx +44 -0
  102. package/src/components/Consent/variables.ts +28 -0
  103. package/src/components/Dropdown/Dropdown.tsx +4 -1
  104. package/src/components/Footer/Footer.tsx +2 -0
  105. package/src/components/JsonViewer/Helpers.tsx +46 -51
  106. package/src/components/JsonViewer/JsonViewer.tsx +23 -25
  107. package/src/components/Markdown/Markdown.tsx +3 -2
  108. package/src/components/Markdown/styles/base-table.ts +14 -1
  109. package/src/components/Markdown/variables.ts +1 -0
  110. package/src/components/Menu/Menu.tsx +5 -0
  111. package/src/components/Menu/MenuSelect.tsx +198 -0
  112. package/src/components/Menu/variables.ts +43 -4
  113. package/src/components/Navbar/Navbar.tsx +22 -5
  114. package/src/components/Navbar/variables.ts +3 -0
  115. package/src/components/NavigationTabs/NavigationTab.tsx +148 -0
  116. package/src/components/NavigationTabs/NavigationTabContent.tsx +66 -0
  117. package/src/components/NavigationTabs/NavigationTabMenu.tsx +40 -0
  118. package/src/components/NavigationTabs/NavigationTabs.tsx +83 -0
  119. package/src/components/NavigationTabs/variables.ts +34 -0
  120. package/src/components/Product/utils.ts +1 -1
  121. package/src/components/Search/SearchAiMessage.tsx +1 -1
  122. package/src/components/Search/SearchAiResponse.tsx +1 -0
  123. package/src/components/Search/SearchTrigger.tsx +11 -2
  124. package/src/components/Sidebar/Sidebar.tsx +2 -2
  125. package/src/components/TableOfContent/TableOfContent.tsx +2 -2
  126. package/src/core/constants/common.ts +1 -0
  127. package/src/core/constants/consent.ts +29 -0
  128. package/src/core/constants/index.ts +1 -0
  129. package/src/core/hooks/__mocks__/use-theme-hooks.ts +17 -0
  130. package/src/core/hooks/index.ts +3 -0
  131. package/src/core/hooks/menu/use-mobile-menu-items.ts +91 -56
  132. package/src/core/hooks/menu/use-mobile-menu-levels.ts +14 -6
  133. package/src/core/hooks/use-consent-dialog.ts +51 -0
  134. package/src/core/hooks/use-consent-telemetry.ts +56 -0
  135. package/src/core/hooks/use-scroll-item-into-view.ts +18 -0
  136. package/src/core/hooks/use-theme-hooks.ts +16 -0
  137. package/src/core/styles/global.ts +14 -2
  138. package/src/core/types/consent.ts +51 -0
  139. package/src/core/types/hooks.ts +7 -0
  140. package/src/core/types/index.ts +2 -0
  141. package/src/core/types/l10n.ts +25 -1
  142. package/src/core/types/navigation-tabs.ts +54 -0
  143. package/src/core/utils/consent-settings-link-placement.ts +18 -0
  144. package/src/core/utils/index.ts +2 -0
  145. package/src/core/utils/mcp.ts +1 -1
  146. package/src/core/utils/menu.ts +54 -9
  147. package/src/core/utils/navigation-tabs.ts +99 -0
  148. package/src/core/utils/urls.ts +3 -1
  149. package/src/icons/ExpandCollapseIcon/ExpandCollapseIcon.tsx +56 -0
  150. package/src/index.ts +7 -0
  151. package/src/layouts/PageLayout.tsx +1 -1
  152. package/src/layouts/RootLayout.tsx +2 -2
  153. package/src/markdoc/components/CodeWalkthrough/CodeFilters.tsx +1 -1
  154. package/src/markdoc/components/CodeWalkthrough/CodePanel.tsx +3 -3
  155. package/src/markdoc/components/CodeWalkthrough/CodeWalkthrough.tsx +1 -1
@@ -0,0 +1,110 @@
1
+ import React from 'react';
2
+ import styled, { keyframes } from 'styled-components';
3
+
4
+ import type { JSX } from 'react';
5
+
6
+ import { Button } from '@redocly/theme/components/Button/Button';
7
+ import { Link } from '@redocly/theme/components/Link/Link';
8
+ import { useThemeHooks } from '@redocly/theme/core/hooks';
9
+
10
+ export function ConsentBanner(): JSX.Element | null {
11
+ const { useConsent, useTranslate } = useThemeHooks();
12
+ const consent = useConsent();
13
+ const { translate } = useTranslate();
14
+
15
+ if (!consent.enabled || consent.mode !== 'opt-in' || consent.decided || consent.isDialogOpen) {
16
+ return null;
17
+ }
18
+
19
+ const title = translate('consent.title', 'We value your privacy');
20
+
21
+ return (
22
+ <BannerWrapper data-component-name="Consent/ConsentBanner" role="region" aria-label={title}>
23
+ <Title>{title}</Title>
24
+ <Message>
25
+ {translate(
26
+ 'consent.message',
27
+ 'By selecting "Accept all cookies", you agree to the use of cookies to improve site navigation, understand how our site is used, and support our marketing efforts. Select "Customize" to choose which types of cookies you allow.',
28
+ )}{' '}
29
+ {consent.config?.privacyPolicyUrl ? (
30
+ <Link to={consent.config.privacyPolicyUrl} external>
31
+ {translate('consent.privacyPolicy', 'Privacy policy')}
32
+ </Link>
33
+ ) : null}
34
+ </Message>
35
+ <Actions>
36
+ <MainActions>
37
+ <Button variant="outlined" onClick={consent.reject}>
38
+ {translate('consent.rejectAll', 'Reject all')}
39
+ </Button>
40
+ <Button variant="primary" onClick={consent.accept}>
41
+ {translate('consent.acceptAllCookies', 'Accept all cookies')}
42
+ </Button>
43
+ </MainActions>
44
+ <Button variant="text" onClick={consent.openDialog}>
45
+ {translate('consent.customize', 'Customize')}
46
+ </Button>
47
+ </Actions>
48
+ </BannerWrapper>
49
+ );
50
+ }
51
+
52
+ const slideUp = keyframes`
53
+ from {
54
+ opacity: 0;
55
+ transform: translateY(16px);
56
+ }
57
+ to {
58
+ opacity: 1;
59
+ transform: translateY(0);
60
+ }
61
+ `;
62
+
63
+ const BannerWrapper = styled.div`
64
+ position: fixed;
65
+ left: var(--consent-banner-offset);
66
+ bottom: var(--consent-banner-offset);
67
+ z-index: var(--consent-banner-z-index);
68
+ display: flex;
69
+ flex-direction: column;
70
+ gap: var(--consent-banner-gap);
71
+ width: var(--consent-banner-width);
72
+ max-width: calc(100vw - 2 * var(--consent-banner-offset));
73
+ padding: var(--consent-banner-padding);
74
+ border: 1px solid var(--consent-banner-border-color);
75
+ border-radius: var(--consent-banner-border-radius);
76
+ background: var(--consent-banner-bg-color);
77
+ color: var(--consent-banner-text-color);
78
+ box-shadow: var(--consent-banner-box-shadow);
79
+ font-family: var(--font-family-base);
80
+
81
+ @media (prefers-reduced-motion: no-preference) {
82
+ animation: ${slideUp} 240ms ease-out;
83
+ }
84
+ `;
85
+
86
+ const Title = styled.h2`
87
+ margin: 0;
88
+ font-size: var(--consent-banner-title-font-size);
89
+ font-weight: var(--font-weight-bold);
90
+ `;
91
+
92
+ const Message = styled.p`
93
+ margin: 0;
94
+ color: var(--text-color-secondary);
95
+ font-size: var(--consent-banner-font-size);
96
+ `;
97
+
98
+ const Actions = styled.div`
99
+ display: flex;
100
+ flex-wrap: wrap;
101
+ gap: var(--consent-banner-gap);
102
+ align-items: center;
103
+ justify-content: space-between;
104
+ `;
105
+
106
+ const MainActions = styled.div`
107
+ display: flex;
108
+ flex-wrap: wrap;
109
+ gap: var(--consent-banner-gap);
110
+ `;
@@ -0,0 +1,210 @@
1
+ import React from 'react';
2
+ import styled from 'styled-components';
3
+
4
+ import type { JSX } from 'react';
5
+ import type { UseConsentResult } from '@redocly/theme/core/types';
6
+ import type { ConsentDraft } from '@redocly/theme/core/hooks';
7
+
8
+ import { Button } from '@redocly/theme/components/Button/Button';
9
+ import { Link } from '@redocly/theme/components/Link/Link';
10
+ import { Portal } from '@redocly/theme/components/Portal/Portal';
11
+ import { Switch } from '@redocly/theme/components/Switch/Switch';
12
+ import { useConsentDialog, useThemeHooks } from '@redocly/theme/core/hooks';
13
+ import { CloseIcon } from '@redocly/theme/icons/CloseIcon/CloseIcon';
14
+
15
+ const noop = () => {};
16
+
17
+ export function ConsentDialog(): JSX.Element | null {
18
+ const { useConsent } = useThemeHooks();
19
+ const consent = useConsent();
20
+
21
+ if (!consent.enabled || !consent.isDialogOpen) {
22
+ return null;
23
+ }
24
+
25
+ return <ConsentDialogContent consent={consent} />;
26
+ }
27
+
28
+ function ConsentDialogContent({ consent }: { consent: UseConsentResult }): JSX.Element {
29
+ const { useTranslate } = useThemeHooks();
30
+ const { translate } = useTranslate();
31
+ const { draft, setCategory, dialogRef, close, save, categories } = useConsentDialog(consent);
32
+
33
+ return (
34
+ <Portal>
35
+ <Overlay
36
+ className="scroll-lock"
37
+ data-component-name="Consent/ConsentDialog"
38
+ onMouseDown={(event) => {
39
+ if (event.target === event.currentTarget) close();
40
+ }}
41
+ >
42
+ <Dialog
43
+ ref={dialogRef}
44
+ role="dialog"
45
+ aria-modal="true"
46
+ aria-labelledby="redocly-consent-dialog-title"
47
+ tabIndex={-1}
48
+ >
49
+ <TitleRow>
50
+ <Title id="redocly-consent-dialog-title">
51
+ {translate('consent.dialog.title', 'Cookie preferences')}
52
+ </Title>
53
+ <Button
54
+ variant="ghost"
55
+ size="small"
56
+ icon={<CloseIcon />}
57
+ aria-label={translate('consent.dialog.close', 'Close')}
58
+ onClick={close}
59
+ />
60
+ </TitleRow>
61
+ <Description>
62
+ {translate(
63
+ 'consent.dialog.description',
64
+ 'Choose which cookies this site may use. You can change your choice at any time.',
65
+ )}{' '}
66
+ {consent.config?.privacyPolicyUrl ? (
67
+ <Link to={consent.config.privacyPolicyUrl} external>
68
+ {translate('consent.privacyPolicy', 'Privacy policy')}
69
+ </Link>
70
+ ) : null}
71
+ </Description>
72
+ {categories.map((category) => {
73
+ const gpcLocked = category.id === 'marketing' && consent.gpc;
74
+ return (
75
+ <Row key={category.id}>
76
+ <RowText>
77
+ <RowTitle>
78
+ {translate(category.titleTranslationKey, category.titleDefault)}
79
+ </RowTitle>
80
+ <RowDescription>
81
+ {translate(category.descriptionTranslationKey, category.descriptionDefault)}
82
+ </RowDescription>
83
+ {gpcLocked ? (
84
+ <GpcNotice>
85
+ {translate(
86
+ 'consent.gpcNotice',
87
+ 'Your browser asked us not to share your data, so this stays off.',
88
+ )}
89
+ </GpcNotice>
90
+ ) : null}
91
+ </RowText>
92
+ {category.locked ? (
93
+ <SwitchGroup role="group" aria-label={translate('consent.alwaysOn', 'Always on')}>
94
+ <Switch value={true} disabled onChange={noop} />
95
+ </SwitchGroup>
96
+ ) : gpcLocked ? (
97
+ <SwitchGroup
98
+ role="group"
99
+ aria-label={translate('consent.alwaysOff', 'Always off')}
100
+ >
101
+ <Switch value={false} disabled onChange={noop} />
102
+ </SwitchGroup>
103
+ ) : (
104
+ <Switch
105
+ value={draft[category.id as keyof ConsentDraft]}
106
+ onChange={(value) => setCategory(category.id as keyof ConsentDraft, value)}
107
+ />
108
+ )}
109
+ </Row>
110
+ );
111
+ })}
112
+ <Actions>
113
+ <Button variant="outlined" onClick={consent.reject}>
114
+ {translate('consent.rejectAll', 'Reject all')}
115
+ </Button>
116
+ <Spacer />
117
+ <Button variant="outlined" onClick={consent.accept}>
118
+ {translate('consent.acceptAll', 'Accept all')}
119
+ </Button>
120
+ <Button variant="primary" onClick={save}>
121
+ {translate('consent.savePreferences', 'Save preferences')}
122
+ </Button>
123
+ </Actions>
124
+ </Dialog>
125
+ </Overlay>
126
+ </Portal>
127
+ );
128
+ }
129
+
130
+ const Overlay = styled.div`
131
+ position: fixed;
132
+ inset: 0;
133
+ z-index: var(--z-index-overlay);
134
+ display: flex;
135
+ align-items: center;
136
+ justify-content: center;
137
+ background: var(--bg-color-modal-overlay);
138
+ font-family: var(--font-family-base);
139
+ `;
140
+
141
+ const Dialog = styled.div`
142
+ width: var(--consent-dialog-width);
143
+ max-width: var(--consent-dialog-max-width);
144
+ padding: var(--consent-dialog-padding);
145
+ border-radius: var(--consent-dialog-border-radius);
146
+ background: var(--consent-dialog-bg-color);
147
+ box-shadow: var(--consent-dialog-box-shadow);
148
+ `;
149
+
150
+ const TitleRow = styled.div`
151
+ display: flex;
152
+ gap: var(--spacing-xs);
153
+ align-items: flex-start;
154
+ justify-content: space-between;
155
+ `;
156
+
157
+ const Title = styled.h2`
158
+ margin: 0 0 var(--spacing-xs);
159
+ font-size: var(--font-size-lg);
160
+ `;
161
+
162
+ const Description = styled.p`
163
+ margin: 0 0 var(--spacing-base);
164
+ color: var(--consent-dialog-description-color);
165
+ font-size: var(--font-size-sm);
166
+ `;
167
+
168
+ const Row = styled.div`
169
+ display: flex;
170
+ gap: var(--spacing-base);
171
+ align-items: flex-start;
172
+ padding: var(--spacing-sm) 0;
173
+ border-top: 1px solid var(--consent-dialog-row-border-color);
174
+ `;
175
+
176
+ const RowText = styled.div`
177
+ flex: 1;
178
+ `;
179
+
180
+ const RowTitle = styled.div`
181
+ font-size: var(--font-size-base);
182
+ font-weight: var(--font-weight-bold);
183
+ `;
184
+
185
+ const RowDescription = styled.div`
186
+ color: var(--consent-dialog-description-color);
187
+ font-size: var(--font-size-sm);
188
+ `;
189
+
190
+ const GpcNotice = styled.div`
191
+ color: var(--consent-dialog-description-color);
192
+ font-size: var(--font-size-sm);
193
+ `;
194
+
195
+ const SwitchGroup = styled.div`
196
+ flex: none;
197
+ `;
198
+
199
+ const Actions = styled.div`
200
+ display: flex;
201
+ flex-wrap: wrap;
202
+ gap: var(--spacing-xs);
203
+ margin-top: var(--spacing-base);
204
+ padding-top: var(--spacing-base);
205
+ border-top: 1px solid var(--consent-dialog-row-border-color);
206
+ `;
207
+
208
+ const Spacer = styled.span`
209
+ flex: 1;
210
+ `;
@@ -0,0 +1,44 @@
1
+ import React from 'react';
2
+ import styled from 'styled-components';
3
+
4
+ import type { JSX } from 'react';
5
+
6
+ import { Button } from '@redocly/theme/components/Button/Button';
7
+ import { useThemeConfig, useThemeHooks } from '@redocly/theme/core/hooks';
8
+ import { resolveSettingsLinkPlacement } from '@redocly/theme/core/utils';
9
+
10
+ export function ConsentSettingsLink(): JSX.Element | null {
11
+ const { useConsent, useTranslate } = useThemeHooks();
12
+ const { consent: consentConfig, footer } = useThemeConfig() || {};
13
+ const consent = useConsent();
14
+ const { translate } = useTranslate();
15
+
16
+ if (!consent.enabled || resolveSettingsLinkPlacement(consentConfig, footer) !== 'footer') {
17
+ return null;
18
+ }
19
+
20
+ return (
21
+ <SettingsLinkButton
22
+ variant="link"
23
+ size="small"
24
+ data-component-name="Consent/ConsentSettingsLink"
25
+ onClick={consent.openDialog}
26
+ >
27
+ {translate('consent.settingsLink', 'Cookie preferences')}
28
+ </SettingsLinkButton>
29
+ );
30
+ }
31
+
32
+ const SettingsLinkButton = styled(Button)`
33
+ margin: var(--footer-link-padding-vertical) 0 var(--footer-link-padding-vertical) var(--spacing-sm);
34
+ padding: 0;
35
+ color: var(--footer-link-text-color);
36
+ font: inherit;
37
+ line-height: inherit;
38
+
39
+ &:hover,
40
+ &:focus-visible {
41
+ color: var(--footer-link-color-hover);
42
+ background: none;
43
+ }
44
+ `;
@@ -0,0 +1,28 @@
1
+ import { css } from 'styled-components';
2
+
3
+ export const consent = css`
4
+ /**
5
+ * @tokens Consent
6
+ */
7
+ --consent-banner-width: 380px;
8
+ --consent-banner-offset: var(--spacing-base); // @presenter Spacing
9
+ --consent-banner-bg-color: var(--bg-color); // @presenter Color
10
+ --consent-banner-text-color: var(--text-color-primary); // @presenter Color
11
+ --consent-banner-border-color: var(--border-color-primary); // @presenter Color
12
+ --consent-banner-border-radius: var(--border-radius-lg);
13
+ --consent-banner-box-shadow: var(--bg-raised-shadow);
14
+ --consent-banner-padding: var(--spacing-base); // @presenter Spacing
15
+ --consent-banner-gap: var(--spacing-sm); // @presenter Spacing
16
+ --consent-banner-title-font-size: var(--font-size-base);
17
+ --consent-banner-font-size: var(--font-size-sm);
18
+ --consent-banner-z-index: var(--z-index-overlay);
19
+
20
+ --consent-dialog-width: 560px;
21
+ --consent-dialog-max-width: calc(100vw - 2 * var(--spacing-base));
22
+ --consent-dialog-padding: var(--spacing-lg); // @presenter Spacing
23
+ --consent-dialog-border-radius: var(--border-radius-lg);
24
+ --consent-dialog-bg-color: var(--modal-bg-color); // @presenter Color
25
+ --consent-dialog-box-shadow: var(--modal-box-shadow);
26
+ --consent-dialog-row-border-color: var(--border-color-primary); // @presenter Color
27
+ --consent-dialog-description-color: var(--text-color-secondary); // @presenter Color
28
+ `;
@@ -156,13 +156,16 @@ export const Dropdown = forwardRef<HTMLDivElement, DropdownProps>(
156
156
 
157
157
  const triggerChild = React.Children.only(trigger) as ReactElement<TriggerProps>;
158
158
 
159
+ const isLinkTrigger = Boolean(triggerChild.props?.to || triggerChild.props?.href);
160
+
159
161
  const dropdownTrigger = cloneElement(triggerChild, {
160
162
  onClick: triggerEvent === 'click' ? handleToggle : undefined,
163
+ 'aria-haspopup': 'menu',
161
164
  'aria-expanded': isOpen,
162
165
  icon: withArrow ? isOpen ? <ChevronUpIcon /> : <ChevronDownIcon /> : undefined,
163
166
  ...(withArrow ? { iconPosition: 'right' } : {}),
164
167
  ...triggerChild.props,
165
- onKeyDown: triggerEvent === 'click' ? handleKeyDown : undefined,
168
+ onKeyDown: triggerEvent === 'click' || !isLinkTrigger ? handleKeyDown : undefined,
166
169
  });
167
170
 
168
171
  const menu = (
@@ -9,6 +9,7 @@ import { breakpoints, isEmptyArray } from '@redocly/theme/core/utils';
9
9
  import { useThemeConfig } from '@redocly/theme/core/hooks';
10
10
  import { FooterCopyright } from '@redocly/theme/components/Footer/FooterCopyright';
11
11
  import { FooterColumn } from '@redocly/theme/components/Footer/FooterColumn';
12
+ import { ConsentSettingsLink } from '@redocly/theme/components/Consent/ConsentSettingsLink';
12
13
 
13
14
  export type FooterProps = {
14
15
  className?: string;
@@ -43,6 +44,7 @@ export function Footer({ className }: FooterProps): JSX.Element | null {
43
44
  )}
44
45
  <FooterCopyrightWrapper>
45
46
  {copyrightText && <FooterCopyright copyrightText={copyrightText} />}
47
+ <ConsentSettingsLink />
46
48
  </FooterCopyrightWrapper>
47
49
  </FooterWrapper>
48
50
  );
@@ -7,40 +7,50 @@ import { Button } from '@redocly/theme/components/Button/Button';
7
7
  import { ShowMoreToggleButton } from '@redocly/theme/components/Button/ShowMoreToggleButton';
8
8
  import { ChevronDownIcon } from '@redocly/theme/icons/ChevronDownIcon/ChevronDownIcon';
9
9
 
10
+ export type JsonFold = {
11
+ isExpanded: (path: string, level: number) => boolean;
12
+ setExpanded: (path: string, expanded: boolean) => void;
13
+ };
14
+
15
+ type Foldable = [path: string, level: number];
16
+
17
+ export function isFoldable(value: unknown): value is Record<string, unknown> {
18
+ return (
19
+ typeof value === 'object' &&
20
+ value !== null &&
21
+ Object.values(value).some((item) => item !== undefined)
22
+ );
23
+ }
24
+
25
+ export function collectFoldables(value: unknown, path = '', level = 1): Foldable[] {
26
+ if (!isFoldable(value)) return [];
27
+ return Object.entries(value).flatMap(([key, child]): Foldable[] => {
28
+ const childPath = `${path}/${key}`;
29
+ return isFoldable(child)
30
+ ? [[childPath, level], ...collectFoldables(child, childPath, level + 1)]
31
+ : [];
32
+ });
33
+ }
34
+
10
35
  export function JsonValue(props: {
11
36
  /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
12
37
  value: any;
13
38
  level: number;
39
+ path: string;
14
40
  standalone?: boolean; // if true, will render the opening and closing punctuation (when it's an item inside array or top-level)
15
- defaultExpandLevel: number;
16
- expandAllSignal: boolean | undefined;
41
+ fold: JsonFold;
17
42
  maxStringLength?: number;
18
43
  }): React.ReactNode {
19
- const { standalone, defaultExpandLevel, level, value, expandAllSignal, maxStringLength } = props;
44
+ const { standalone, fold, level, path, value, maxStringLength } = props;
20
45
  const indent = ' '.repeat(level * 2);
21
-
22
- const [isExpandedState, setExpanded] = React.useState(
23
- expandAllSignal ?? level < defaultExpandLevel,
24
- );
25
- const isExpanded = expandAllSignal !== undefined && level > 0 ? expandAllSignal : isExpandedState;
26
- const [prevExpandAllSignal, setPrevExpandAllSignal] = React.useState(expandAllSignal);
27
- const [prevLevel, setPrevLevel] = React.useState(level);
28
-
29
- if (prevExpandAllSignal !== expandAllSignal || prevLevel !== level) {
30
- setPrevExpandAllSignal(expandAllSignal);
31
- setPrevLevel(level);
32
- if (expandAllSignal !== undefined && !(expandAllSignal === false && level === 0)) {
33
- setExpanded(expandAllSignal);
34
- }
35
- }
46
+ const isExpanded = fold.isExpanded(path, level);
36
47
 
37
48
  const keys: string[] =
38
49
  (value && Object.keys(value).filter((key) => value[key] !== undefined)) || []; // filter out undefined properties
39
50
 
40
51
  const valueType = typeof value;
41
- const isPrimitive = (valueType !== 'object' && value !== null) || keys.length === 0;
42
52
 
43
- if (isPrimitive) {
53
+ if (!isFoldable(value)) {
44
54
  const isOversizedString =
45
55
  typeof value === 'string' && maxStringLength !== undefined && value.length > maxStringLength;
46
56
  return (
@@ -64,9 +74,9 @@ export function JsonValue(props: {
64
74
  <JsonValue
65
75
  value={item}
66
76
  level={level + 1}
77
+ path={`${path}/${index}`}
67
78
  standalone={true}
68
- defaultExpandLevel={defaultExpandLevel}
69
- expandAllSignal={expandAllSignal}
79
+ fold={fold}
70
80
  maxStringLength={maxStringLength}
71
81
  />
72
82
  {index < value.length - 1 ? ',\n' : null}
@@ -78,8 +88,8 @@ export function JsonValue(props: {
78
88
  name={key}
79
89
  value={value[key]}
80
90
  level={level + 1}
81
- defaultExpandLevel={defaultExpandLevel}
82
- expandAllSignal={expandAllSignal}
91
+ path={`${path}/${key}`}
92
+ fold={fold}
83
93
  maxStringLength={maxStringLength}
84
94
  />
85
95
  {index < keys.length - 1 ? ',\n' : null}
@@ -90,7 +100,7 @@ export function JsonValue(props: {
90
100
  <>
91
101
  {indent}
92
102
  {level > 0 ? (
93
- <ExpandIcon onClick={() => setExpanded((v) => !v)} isExpanded={isExpanded} />
103
+ <ExpandIcon onClick={() => fold.setExpanded(path, !isExpanded)} isExpanded={isExpanded} />
94
104
  ) : null}
95
105
  {openingPunctuation}
96
106
  {isExpanded ? '\n' : null}
@@ -119,33 +129,18 @@ function JsonKeyValue(props: {
119
129
  /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
120
130
  value: any;
121
131
  level: number;
122
- defaultExpandLevel: number;
123
- expandAllSignal: boolean | undefined;
132
+ path: string;
133
+ fold: JsonFold;
124
134
  maxStringLength?: number;
125
135
  }): React.ReactNode {
126
- const { defaultExpandLevel, level, name, value, expandAllSignal, maxStringLength } = props;
136
+ const { fold, level, name, path, value, maxStringLength } = props;
127
137
  const indent = ' '.repeat(level * 2);
128
- const keys: string[] =
129
- (value && Object.keys(value).filter((key) => value[key] !== undefined)) || []; // filter out undefined properties
130
-
131
- const isCollapsible = typeof value === 'object' && value !== null && keys.length > 0;
132
- const [isExpandedState, setExpanded] = React.useState(
133
- expandAllSignal ?? level < defaultExpandLevel,
134
- );
135
- const isExpanded = expandAllSignal !== undefined ? expandAllSignal : isExpandedState;
136
- const [prevExpandAllSignal, setPrevExpandAllSignal] = React.useState(expandAllSignal);
137
-
138
- if (prevExpandAllSignal !== expandAllSignal) {
139
- setPrevExpandAllSignal(expandAllSignal);
140
- if (expandAllSignal !== undefined) {
141
- setExpanded(expandAllSignal);
142
- }
143
- }
138
+ const isExpanded = fold.isExpanded(path, level);
144
139
 
145
140
  const openingPunctuation = Array.isArray(value) ? '[' : '{';
146
141
  const closingPunctuation = Array.isArray(value) ? ']' : '}';
147
142
 
148
- const valueElement = isCollapsible ? (
143
+ const valueElement = isFoldable(value) ? (
149
144
  <>
150
145
  {openingPunctuation}
151
146
  {isExpanded ? (
@@ -154,8 +149,8 @@ function JsonKeyValue(props: {
154
149
  <JsonValue
155
150
  value={value}
156
151
  level={level}
157
- defaultExpandLevel={defaultExpandLevel}
158
- expandAllSignal={expandAllSignal}
152
+ path={path}
153
+ fold={fold}
159
154
  maxStringLength={maxStringLength}
160
155
  />
161
156
  {'\n'}
@@ -170,8 +165,8 @@ function JsonKeyValue(props: {
170
165
  <JsonValue
171
166
  value={value}
172
167
  level={level}
173
- defaultExpandLevel={defaultExpandLevel}
174
- expandAllSignal={expandAllSignal}
168
+ path={path}
169
+ fold={fold}
175
170
  maxStringLength={maxStringLength}
176
171
  />
177
172
  );
@@ -179,8 +174,8 @@ function JsonKeyValue(props: {
179
174
  return (
180
175
  <>
181
176
  {indent}
182
- {isCollapsible ? (
183
- <ExpandIcon onClick={() => setExpanded((v) => !v)} isExpanded={isExpanded} />
177
+ {isFoldable(value) ? (
178
+ <ExpandIcon onClick={() => fold.setExpanded(path, !isExpanded)} isExpanded={isExpanded} />
184
179
  ) : null}
185
180
  <span className="property token string">{`"${name}"`}</span>: {valueElement}
186
181
  </>
@@ -5,7 +5,8 @@ import type { JSX } from 'react';
5
5
  import type { CodeBlockControlsProps } from '@redocly/theme/components/CodeBlock/CodeBlockControls';
6
6
 
7
7
  import { CodeBlock } from '@redocly/theme/components/CodeBlock/CodeBlock';
8
- import { JsonValue } from '@redocly/theme/components/JsonViewer/Helpers';
8
+ import { collectFoldables, JsonValue } from '@redocly/theme/components/JsonViewer/Helpers';
9
+ import type { JsonFold } from '@redocly/theme/components/JsonViewer/Helpers';
9
10
 
10
11
  export type PanelType = 'request' | 'responses' | 'request-samples' | 'response-samples';
11
12
 
@@ -33,25 +34,20 @@ function JsonComponent({
33
34
  controls = {},
34
35
  maxStringLength,
35
36
  }: JsonProps): JSX.Element {
36
- const showFoldingButtons =
37
- data && Object.values(data).some((value) => typeof value === 'object' && value !== null);
37
+ const defaultExpandLevel = Math.max(1, expandLevel);
38
+ const foldables = React.useMemo(() => collectFoldables(data), [data]);
39
+ const [expandedPaths, setExpandedPaths] = React.useState<Record<string, boolean>>({});
38
40
 
39
- const [expandAllSignal, setExpandAllSignal] = React.useState<boolean | undefined>(undefined);
40
-
41
- const expandAll = () => {
42
- setExpandAllSignal(true);
43
- onPanelToggle?.(true);
44
- setTimeout(() => {
45
- setExpandAllSignal(undefined);
46
- });
41
+ const fold: JsonFold = {
42
+ isExpanded: (path, level) => expandedPaths[path] ?? level < defaultExpandLevel,
43
+ setExpanded: (path, expanded) => setExpandedPaths((prev) => ({ ...prev, [path]: expanded })),
47
44
  };
45
+ const hasFoldables = foldables.length > 0;
46
+ const allExpanded = foldables.every(([path, level]) => fold.isExpanded(path, level));
48
47
 
49
- const collapseAll = () => {
50
- setExpandAllSignal(false);
51
- onPanelToggle?.(false);
52
- setTimeout(() => {
53
- setExpandAllSignal(undefined);
54
- });
48
+ const toggleAll = () => {
49
+ onPanelToggle?.(!allExpanded);
50
+ setExpandedPaths(Object.fromEntries(foldables.map(([path]) => [path, !allExpanded])));
55
51
  };
56
52
 
57
53
  const source = JSON.stringify(data, null, 2);
@@ -73,12 +69,14 @@ function JsonComponent({
73
69
  controls: controls && {
74
70
  ...controls,
75
71
  copy: { ...controls.copy, data, onClick: onCopyClick, handleOutside: true },
76
- expand: showFoldingButtons
77
- ? { ...controls.expand, onClick: expandAll }
78
- : undefined,
79
- collapse: showFoldingButtons
80
- ? { ...controls.collapse, onClick: collapseAll }
81
- : undefined,
72
+ expand:
73
+ hasFoldables && !allExpanded
74
+ ? { ...controls.expand, onClick: toggleAll }
75
+ : undefined,
76
+ collapse:
77
+ hasFoldables && allExpanded
78
+ ? { ...controls.collapse, onClick: toggleAll }
79
+ : undefined,
82
80
  },
83
81
  }
84
82
  : undefined
@@ -89,9 +87,9 @@ function JsonComponent({
89
87
  <JsonValue
90
88
  value={data}
91
89
  level={0}
90
+ path=""
92
91
  standalone={true}
93
- expandAllSignal={expandAllSignal}
94
- defaultExpandLevel={Math.max(1, expandLevel)}
92
+ fold={fold}
95
93
  maxStringLength={maxStringLength}
96
94
  />
97
95
  </FoldingWrap>