@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.
- package/lib/components/Catalog/Catalog.js +1 -1
- package/lib/components/Catalog/CatalogEntity/CatalogEntityHistory/CatalogEntityHistorySidebar.js +2 -2
- package/lib/components/Catalog/CatalogEntity/CatalogEntitySchema.js +1 -1
- package/lib/components/Catalog/CatalogMobileFiltersPanel/CatalogMobileFiltersPanel.js +1 -1
- package/lib/components/Catalog/CatalogMobileFiltersPanel/CatalogMobileFiltersPanelOverlay.js +2 -2
- package/lib/components/CodeBlock/CodeBlockControls.js +3 -5
- package/lib/components/Consent/Consent.d.ts +2 -0
- package/lib/components/Consent/Consent.js +17 -0
- package/lib/components/Consent/ConsentBanner.d.ts +2 -0
- package/lib/components/Consent/ConsentBanner.js +120 -0
- package/lib/components/Consent/ConsentDialog.d.ts +2 -0
- package/lib/components/Consent/ConsentDialog.js +128 -0
- package/lib/components/Consent/ConsentSettingsLink.d.ts +2 -0
- package/lib/components/Consent/ConsentSettingsLink.js +35 -0
- package/lib/components/Consent/variables.d.ts +1 -0
- package/lib/components/Consent/variables.js +31 -0
- package/lib/components/Dropdown/Dropdown.js +3 -1
- package/lib/components/Footer/Footer.js +4 -1
- package/lib/components/JsonViewer/Helpers.d.ts +10 -2
- package/lib/components/JsonViewer/Helpers.js +29 -33
- package/lib/components/JsonViewer/JsonViewer.js +15 -18
- package/lib/components/Markdown/Markdown.js +3 -2
- package/lib/components/Markdown/styles/base-table.js +14 -1
- package/lib/components/Markdown/variables.js +1 -0
- package/lib/components/Menu/Menu.js +4 -0
- package/lib/components/Menu/MenuSelect.d.ts +7 -0
- package/lib/components/Menu/MenuSelect.js +129 -0
- package/lib/components/Menu/variables.js +43 -4
- package/lib/components/Navbar/Navbar.js +44 -5
- package/lib/components/Navbar/variables.js +3 -0
- package/lib/components/NavigationTabs/NavigationTab.d.ts +7 -0
- package/lib/components/NavigationTabs/NavigationTab.js +93 -0
- package/lib/components/NavigationTabs/NavigationTabContent.d.ts +7 -0
- package/lib/components/NavigationTabs/NavigationTabContent.js +50 -0
- package/lib/components/NavigationTabs/NavigationTabMenu.d.ts +7 -0
- package/lib/components/NavigationTabs/NavigationTabMenu.js +18 -0
- package/lib/components/NavigationTabs/NavigationTabs.d.ts +7 -0
- package/lib/components/NavigationTabs/NavigationTabs.js +97 -0
- package/lib/components/NavigationTabs/variables.d.ts +1 -0
- package/lib/components/NavigationTabs/variables.js +37 -0
- package/lib/components/Product/utils.js +1 -1
- package/lib/components/Search/SearchAiMessage.js +1 -1
- package/lib/components/Search/SearchAiResponse.js +1 -0
- package/lib/components/Search/SearchTrigger.js +8 -2
- package/lib/components/Sidebar/Sidebar.js +2 -2
- package/lib/components/TableOfContent/TableOfContent.js +2 -2
- package/lib/core/constants/common.d.ts +1 -0
- package/lib/core/constants/common.js +1 -0
- package/lib/core/constants/consent.d.ts +2 -0
- package/lib/core/constants/consent.js +30 -0
- package/lib/core/constants/index.d.ts +1 -0
- package/lib/core/constants/index.js +1 -0
- package/lib/core/hooks/index.d.ts +3 -0
- package/lib/core/hooks/index.js +3 -0
- package/lib/core/hooks/menu/use-mobile-menu-items.js +75 -51
- package/lib/core/hooks/menu/use-mobile-menu-levels.js +12 -7
- package/lib/core/hooks/use-consent-dialog.d.ts +12 -0
- package/lib/core/hooks/use-consent-dialog.js +36 -0
- package/lib/core/hooks/use-consent-telemetry.d.ts +1 -0
- package/lib/core/hooks/use-consent-telemetry.js +42 -0
- package/lib/core/hooks/use-scroll-item-into-view.d.ts +2 -0
- package/lib/core/hooks/use-scroll-item-into-view.js +16 -0
- package/lib/core/hooks/use-theme-hooks.js +16 -0
- package/lib/core/styles/global.js +54 -42
- package/lib/core/types/consent.d.ts +54 -0
- package/lib/core/types/consent.js +3 -0
- package/lib/core/types/hooks.d.ts +7 -0
- package/lib/core/types/index.d.ts +2 -0
- package/lib/core/types/l10n.d.ts +1 -1
- package/lib/core/types/navigation-tabs.d.ts +49 -0
- package/lib/core/types/navigation-tabs.js +3 -0
- package/lib/core/utils/consent-settings-link-placement.d.ts +3 -0
- package/lib/core/utils/consent-settings-link-placement.js +15 -0
- package/lib/core/utils/index.d.ts +2 -0
- package/lib/core/utils/index.js +2 -0
- package/lib/core/utils/mcp.js +1 -1
- package/lib/core/utils/menu.d.ts +4 -0
- package/lib/core/utils/menu.js +48 -8
- package/lib/core/utils/navigation-tabs.d.ts +8 -0
- package/lib/core/utils/navigation-tabs.js +71 -0
- package/lib/core/utils/urls.js +3 -1
- package/lib/icons/ExpandCollapseIcon/ExpandCollapseIcon.d.ts +29 -0
- package/lib/icons/ExpandCollapseIcon/ExpandCollapseIcon.js +81 -0
- package/lib/index.d.ts +6 -0
- package/lib/index.js +8 -1
- package/lib/layouts/PageLayout.js +1 -1
- package/lib/layouts/RootLayout.js +2 -2
- package/lib/markdoc/components/CodeWalkthrough/CodeFilters.js +1 -1
- package/lib/markdoc/components/CodeWalkthrough/CodePanel.js +3 -3
- package/lib/markdoc/components/CodeWalkthrough/CodeWalkthrough.js +1 -1
- package/package.json +3 -3
- package/src/components/Catalog/Catalog.tsx +1 -1
- package/src/components/Catalog/CatalogEntity/CatalogEntityHistory/CatalogEntityHistorySidebar.tsx +2 -2
- package/src/components/Catalog/CatalogEntity/CatalogEntitySchema.tsx +1 -1
- package/src/components/Catalog/CatalogMobileFiltersPanel/CatalogMobileFiltersPanel.tsx +1 -1
- package/src/components/Catalog/CatalogMobileFiltersPanel/CatalogMobileFiltersPanelOverlay.tsx +2 -2
- package/src/components/CodeBlock/CodeBlockControls.tsx +4 -7
- package/src/components/Consent/Consent.tsx +18 -0
- package/src/components/Consent/ConsentBanner.tsx +110 -0
- package/src/components/Consent/ConsentDialog.tsx +210 -0
- package/src/components/Consent/ConsentSettingsLink.tsx +44 -0
- package/src/components/Consent/variables.ts +28 -0
- package/src/components/Dropdown/Dropdown.tsx +4 -1
- package/src/components/Footer/Footer.tsx +2 -0
- package/src/components/JsonViewer/Helpers.tsx +46 -51
- package/src/components/JsonViewer/JsonViewer.tsx +23 -25
- package/src/components/Markdown/Markdown.tsx +3 -2
- package/src/components/Markdown/styles/base-table.ts +14 -1
- package/src/components/Markdown/variables.ts +1 -0
- package/src/components/Menu/Menu.tsx +5 -0
- package/src/components/Menu/MenuSelect.tsx +198 -0
- package/src/components/Menu/variables.ts +43 -4
- package/src/components/Navbar/Navbar.tsx +22 -5
- package/src/components/Navbar/variables.ts +3 -0
- package/src/components/NavigationTabs/NavigationTab.tsx +148 -0
- package/src/components/NavigationTabs/NavigationTabContent.tsx +66 -0
- package/src/components/NavigationTabs/NavigationTabMenu.tsx +40 -0
- package/src/components/NavigationTabs/NavigationTabs.tsx +83 -0
- package/src/components/NavigationTabs/variables.ts +34 -0
- package/src/components/Product/utils.ts +1 -1
- package/src/components/Search/SearchAiMessage.tsx +1 -1
- package/src/components/Search/SearchAiResponse.tsx +1 -0
- package/src/components/Search/SearchTrigger.tsx +11 -2
- package/src/components/Sidebar/Sidebar.tsx +2 -2
- package/src/components/TableOfContent/TableOfContent.tsx +2 -2
- package/src/core/constants/common.ts +1 -0
- package/src/core/constants/consent.ts +29 -0
- package/src/core/constants/index.ts +1 -0
- package/src/core/hooks/__mocks__/use-theme-hooks.ts +17 -0
- package/src/core/hooks/index.ts +3 -0
- package/src/core/hooks/menu/use-mobile-menu-items.ts +91 -56
- package/src/core/hooks/menu/use-mobile-menu-levels.ts +14 -6
- package/src/core/hooks/use-consent-dialog.ts +51 -0
- package/src/core/hooks/use-consent-telemetry.ts +56 -0
- package/src/core/hooks/use-scroll-item-into-view.ts +18 -0
- package/src/core/hooks/use-theme-hooks.ts +16 -0
- package/src/core/styles/global.ts +14 -2
- package/src/core/types/consent.ts +51 -0
- package/src/core/types/hooks.ts +7 -0
- package/src/core/types/index.ts +2 -0
- package/src/core/types/l10n.ts +25 -1
- package/src/core/types/navigation-tabs.ts +54 -0
- package/src/core/utils/consent-settings-link-placement.ts +18 -0
- package/src/core/utils/index.ts +2 -0
- package/src/core/utils/mcp.ts +1 -1
- package/src/core/utils/menu.ts +54 -9
- package/src/core/utils/navigation-tabs.ts +99 -0
- package/src/core/utils/urls.ts +3 -1
- package/src/icons/ExpandCollapseIcon/ExpandCollapseIcon.tsx +56 -0
- package/src/index.ts +7 -0
- package/src/layouts/PageLayout.tsx +1 -1
- package/src/layouts/RootLayout.tsx +2 -2
- package/src/markdoc/components/CodeWalkthrough/CodeFilters.tsx +1 -1
- package/src/markdoc/components/CodeWalkthrough/CodePanel.tsx +3 -3
- 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
|
-
|
|
16
|
-
expandAllSignal: boolean | undefined;
|
|
41
|
+
fold: JsonFold;
|
|
17
42
|
maxStringLength?: number;
|
|
18
43
|
}): React.ReactNode {
|
|
19
|
-
const { standalone,
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
82
|
-
|
|
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(
|
|
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
|
-
|
|
123
|
-
|
|
132
|
+
path: string;
|
|
133
|
+
fold: JsonFold;
|
|
124
134
|
maxStringLength?: number;
|
|
125
135
|
}): React.ReactNode {
|
|
126
|
-
const {
|
|
136
|
+
const { fold, level, name, path, value, maxStringLength } = props;
|
|
127
137
|
const indent = ' '.repeat(level * 2);
|
|
128
|
-
const
|
|
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 =
|
|
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
|
-
|
|
158
|
-
|
|
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
|
-
|
|
174
|
-
|
|
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
|
-
{
|
|
183
|
-
<ExpandIcon onClick={() => setExpanded(
|
|
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
|
|
37
|
-
|
|
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
|
|
40
|
-
|
|
41
|
-
|
|
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
|
|
50
|
-
|
|
51
|
-
|
|
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:
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
-
|
|
94
|
-
defaultExpandLevel={Math.max(1, expandLevel)}
|
|
92
|
+
fold={fold}
|
|
95
93
|
maxStringLength={maxStringLength}
|
|
96
94
|
/>
|
|
97
95
|
</FoldingWrap>
|