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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/lib/components/CodeBlock/CodeBlockControls.js +3 -5
  2. package/lib/components/Consent/Consent.d.ts +2 -0
  3. package/lib/components/Consent/Consent.js +17 -0
  4. package/lib/components/Consent/ConsentBanner.d.ts +2 -0
  5. package/lib/components/Consent/ConsentBanner.js +120 -0
  6. package/lib/components/Consent/ConsentDialog.d.ts +2 -0
  7. package/lib/components/Consent/ConsentDialog.js +128 -0
  8. package/lib/components/Consent/ConsentSettingsLink.d.ts +2 -0
  9. package/lib/components/Consent/ConsentSettingsLink.js +35 -0
  10. package/lib/components/Consent/variables.d.ts +1 -0
  11. package/lib/components/Consent/variables.js +31 -0
  12. package/lib/components/Footer/Footer.js +4 -1
  13. package/lib/components/JsonViewer/Helpers.d.ts +10 -2
  14. package/lib/components/JsonViewer/Helpers.js +29 -33
  15. package/lib/components/JsonViewer/JsonViewer.js +15 -18
  16. package/lib/components/Markdown/styles/base-table.js +14 -1
  17. package/lib/components/Markdown/variables.js +1 -0
  18. package/lib/components/Product/utils.js +1 -1
  19. package/lib/components/Search/SearchAiMessage.js +1 -1
  20. package/lib/components/Search/SearchAiResponse.js +1 -0
  21. package/lib/core/constants/consent.d.ts +2 -0
  22. package/lib/core/constants/consent.js +30 -0
  23. package/lib/core/constants/index.d.ts +1 -0
  24. package/lib/core/constants/index.js +1 -0
  25. package/lib/core/hooks/index.d.ts +2 -0
  26. package/lib/core/hooks/index.js +2 -0
  27. package/lib/core/hooks/use-consent-dialog.d.ts +12 -0
  28. package/lib/core/hooks/use-consent-dialog.js +36 -0
  29. package/lib/core/hooks/use-consent-telemetry.d.ts +1 -0
  30. package/lib/core/hooks/use-consent-telemetry.js +42 -0
  31. package/lib/core/hooks/use-theme-hooks.js +14 -0
  32. package/lib/core/styles/global.js +42 -40
  33. package/lib/core/types/consent.d.ts +54 -0
  34. package/lib/core/types/consent.js +3 -0
  35. package/lib/core/types/hooks.d.ts +2 -0
  36. package/lib/core/types/index.d.ts +1 -0
  37. package/lib/core/types/l10n.d.ts +1 -1
  38. package/lib/core/utils/consent-settings-link-placement.d.ts +3 -0
  39. package/lib/core/utils/consent-settings-link-placement.js +15 -0
  40. package/lib/core/utils/index.d.ts +1 -0
  41. package/lib/core/utils/index.js +1 -0
  42. package/lib/core/utils/mcp.js +1 -1
  43. package/lib/core/utils/menu.js +6 -5
  44. package/lib/core/utils/urls.js +3 -1
  45. package/lib/icons/ExpandCollapseIcon/ExpandCollapseIcon.d.ts +29 -0
  46. package/lib/icons/ExpandCollapseIcon/ExpandCollapseIcon.js +81 -0
  47. package/lib/index.d.ts +1 -0
  48. package/lib/index.js +1 -0
  49. package/package.json +3 -3
  50. package/src/components/CodeBlock/CodeBlockControls.tsx +4 -7
  51. package/src/components/Consent/Consent.tsx +18 -0
  52. package/src/components/Consent/ConsentBanner.tsx +110 -0
  53. package/src/components/Consent/ConsentDialog.tsx +210 -0
  54. package/src/components/Consent/ConsentSettingsLink.tsx +44 -0
  55. package/src/components/Consent/variables.ts +28 -0
  56. package/src/components/Footer/Footer.tsx +2 -0
  57. package/src/components/JsonViewer/Helpers.tsx +46 -51
  58. package/src/components/JsonViewer/JsonViewer.tsx +23 -25
  59. package/src/components/Markdown/styles/base-table.ts +14 -1
  60. package/src/components/Markdown/variables.ts +1 -0
  61. package/src/components/Product/utils.ts +1 -1
  62. package/src/components/Search/SearchAiMessage.tsx +1 -1
  63. package/src/components/Search/SearchAiResponse.tsx +1 -0
  64. package/src/core/constants/consent.ts +29 -0
  65. package/src/core/constants/index.ts +1 -0
  66. package/src/core/hooks/__mocks__/use-theme-hooks.ts +15 -0
  67. package/src/core/hooks/index.ts +2 -0
  68. package/src/core/hooks/use-consent-dialog.ts +51 -0
  69. package/src/core/hooks/use-consent-telemetry.ts +56 -0
  70. package/src/core/hooks/use-theme-hooks.ts +14 -0
  71. package/src/core/styles/global.ts +2 -0
  72. package/src/core/types/consent.ts +51 -0
  73. package/src/core/types/hooks.ts +2 -0
  74. package/src/core/types/index.ts +1 -0
  75. package/src/core/types/l10n.ts +22 -1
  76. package/src/core/utils/consent-settings-link-placement.ts +18 -0
  77. package/src/core/utils/index.ts +1 -0
  78. package/src/core/utils/mcp.ts +1 -1
  79. package/src/core/utils/menu.ts +5 -6
  80. package/src/core/utils/urls.ts +3 -1
  81. package/src/icons/ExpandCollapseIcon/ExpandCollapseIcon.tsx +56 -0
  82. package/src/index.ts +1 -0
@@ -0,0 +1,29 @@
1
+ import React from 'react';
2
+ import type { IconProps } from '../../icons/types';
3
+ declare const Icon: (props: IconProps) => React.JSX.Element;
4
+ export declare const ExpandCollapseIcon: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<import("styled-components").FastOmit<import("styled-components").FastOmit<{
5
+ color?: string;
6
+ size?: string;
7
+ className?: string;
8
+ 'data-component-name'?: string;
9
+ } & React.SVGProps<SVGSVGElement>, "data-component-name" | "size" | keyof React.SVGProps<SVGSVGElement>> & {
10
+ color?: string;
11
+ size?: string;
12
+ className?: string;
13
+ 'data-component-name'?: string;
14
+ } & React.SVGProps<SVGSVGElement>, "$collapse"> & {
15
+ $collapse?: boolean;
16
+ }, "data-component-name"> & Partial<Pick<import("styled-components").FastOmit<import("styled-components").FastOmit<{
17
+ color?: string;
18
+ size?: string;
19
+ className?: string;
20
+ 'data-component-name'?: string;
21
+ } & React.SVGProps<SVGSVGElement>, "data-component-name" | "size" | keyof React.SVGProps<SVGSVGElement>> & {
22
+ color?: string;
23
+ size?: string;
24
+ className?: string;
25
+ 'data-component-name'?: string;
26
+ } & React.SVGProps<SVGSVGElement>, "$collapse"> & {
27
+ $collapse?: boolean;
28
+ }, "data-component-name">>> & string & Omit<typeof Icon, keyof React.Component<any, {}, any>>;
29
+ export {};
@@ -0,0 +1,81 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.ExpandCollapseIcon = void 0;
40
+ const react_1 = __importDefault(require("react"));
41
+ const styled_components_1 = __importStar(require("styled-components"));
42
+ const utils_1 = require("../../core/utils");
43
+ const Icon = (props) => (react_1.default.createElement("svg", Object.assign({ viewBox: "0 0 16 16", fill: "none", xmlns: "http://www.w3.org/2000/svg" }, props),
44
+ react_1.default.createElement("path", { className: "corner-top", d: "M12.2432 8.70703H11.2461L11.2422 4.75781L7.29297 4.75391V3.75781H12.2432V8.70703Z" }),
45
+ react_1.default.createElement("path", { className: "corner-bottom", d: "M4.75781 11.2422L8.70703 11.2461V12.2432H3.75781V7.29297H4.75391L4.75781 11.2422Z" })));
46
+ exports.ExpandCollapseIcon = (0, styled_components_1.default)(Icon).attrs({
47
+ 'data-component-name': 'icons/ExpandCollapseIcon/ExpandCollapseIcon',
48
+ }) `
49
+ path {
50
+ fill: ${({ color }) => (0, utils_1.getCssColorVariable)(color)};
51
+ transform-box: view-box;
52
+ transition: transform 0.3s ease-in-out;
53
+
54
+ @media (prefers-reduced-motion: reduce) {
55
+ transition: none;
56
+ }
57
+ }
58
+
59
+ .corner-top {
60
+ transform-origin: 10.756px 5.244px;
61
+ }
62
+
63
+ .corner-bottom {
64
+ transform-origin: 5.244px 10.756px;
65
+ }
66
+
67
+ ${({ $collapse }) => $collapse &&
68
+ (0, styled_components_1.css) `
69
+ .corner-top {
70
+ transform: rotate(180deg);
71
+ }
72
+
73
+ .corner-bottom {
74
+ transform: rotate(-180deg);
75
+ }
76
+ `}
77
+
78
+ height: ${({ size }) => size || '16px'};
79
+ width: ${({ size }) => size || '16px'};
80
+ `;
81
+ //# sourceMappingURL=ExpandCollapseIcon.js.map
package/lib/index.d.ts CHANGED
@@ -226,6 +226,7 @@ export * from './icons/LinkIcon/LinkIcon';
226
226
  export * from './icons/LaunchIcon/LaunchIcon';
227
227
  export * from './icons/MinimizeIcon/MinimizeIcon';
228
228
  export * from './icons/MaximizeIcon/MaximizeIcon';
229
+ export * from './icons/ExpandCollapseIcon/ExpandCollapseIcon';
229
230
  export * from './icons/MarkdownFullIcon/MarkdownFullIcon';
230
231
  export * from './icons/DocumentBlankIcon/DocumentBlankIcon';
231
232
  export * from './icons/WarningSquareIcon/WarningSquareIcon';
package/lib/index.js CHANGED
@@ -289,6 +289,7 @@ __exportStar(require("./icons/LinkIcon/LinkIcon"), exports);
289
289
  __exportStar(require("./icons/LaunchIcon/LaunchIcon"), exports);
290
290
  __exportStar(require("./icons/MinimizeIcon/MinimizeIcon"), exports);
291
291
  __exportStar(require("./icons/MaximizeIcon/MaximizeIcon"), exports);
292
+ __exportStar(require("./icons/ExpandCollapseIcon/ExpandCollapseIcon"), exports);
292
293
  __exportStar(require("./icons/MarkdownFullIcon/MarkdownFullIcon"), exports);
293
294
  __exportStar(require("./icons/DocumentBlankIcon/DocumentBlankIcon"), exports);
294
295
  __exportStar(require("./icons/WarningSquareIcon/WarningSquareIcon"), exports);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@redocly/theme",
3
- "version": "0.70.0-next.2",
3
+ "version": "0.70.0-next.3",
4
4
  "description": "Shared UI components lib",
5
5
  "keywords": [
6
6
  "theme",
@@ -42,7 +42,7 @@
42
42
  "devDependencies": {
43
43
  "@babel/core": "7.29.7",
44
44
  "@markdoc/markdoc": "0.5.2",
45
- "@redocly/realm-asyncapi-sdk": "0.16.0-next.0",
45
+ "@redocly/realm-asyncapi-sdk": "0.16.0-next.1",
46
46
  "@shikijs/types": "3.21.0",
47
47
  "@testing-library/react": "16.3.0",
48
48
  "@testing-library/user-event": "14.6.1",
@@ -71,7 +71,7 @@
71
71
  "webpack": "5.105.2"
72
72
  },
73
73
  "dependencies": {
74
- "@redocly/config": "0.57.0",
74
+ "@redocly/config": "0.58.0",
75
75
  "@tanstack/react-query": "5.62.3",
76
76
  "@tanstack/react-virtual": "3.13.0",
77
77
  "@xyflow/react": "12.8.3",
@@ -10,8 +10,7 @@ import { Tooltip } from '@redocly/theme/components/Tooltip/Tooltip';
10
10
  import type { ReportButtonProps } from '@redocly/theme/core/hooks';
11
11
  import { useThemeHooks, useThemeConfig } from '@redocly/theme/core/hooks';
12
12
  import { DeselectIcon } from '@redocly/theme/icons/DeselectIcon/DeselectIcon';
13
- import { MaximizeIcon } from '@redocly/theme/icons/MaximizeIcon/MaximizeIcon';
14
- import { MinimizeIcon } from '@redocly/theme/icons/MinimizeIcon/MinimizeIcon';
13
+ import { ExpandCollapseIcon } from '@redocly/theme/icons/ExpandCollapseIcon/ExpandCollapseIcon';
15
14
  import { SelectIcon } from '@redocly/theme/icons/SelectIcon/SelectIcon';
16
15
  import { WarningSquareIcon } from '@redocly/theme/icons/WarningSquareIcon/WarningSquareIcon';
17
16
  import { Button } from '@redocly/theme/components/Button/Button';
@@ -116,16 +115,14 @@ export function CodeBlockControls({
116
115
  variant="text"
117
116
  size="small"
118
117
  data-testid="expand-all"
119
- icon={controlsType === 'icon' ? <MaximizeIcon /> : undefined}
118
+ icon={controlsType === 'icon' ? <ExpandCollapseIcon /> : undefined}
120
119
  aria-label="Expand all"
121
120
  onClick={expand?.onClick}
122
121
  >
123
122
  {controlsType !== 'icon' && (expand?.label || 'Expand all')}
124
123
  </ControlButton>
125
124
  </Tooltip>
126
- ) : null}
127
-
128
- {collapse && !codeSnippet?.collapse?.hide ? (
125
+ ) : collapse && !codeSnippet?.collapse?.hide ? (
129
126
  <Tooltip
130
127
  tip={
131
128
  collapse.props?.tooltip ||
@@ -138,7 +135,7 @@ export function CodeBlockControls({
138
135
  variant="text"
139
136
  size="small"
140
137
  data-testid="collapse-all"
141
- icon={controlsType === 'icon' ? <MinimizeIcon /> : undefined}
138
+ icon={controlsType === 'icon' ? <ExpandCollapseIcon $collapse /> : undefined}
142
139
  onClick={collapse?.onClick}
143
140
  aria-label="Collapse all"
144
141
  >
@@ -0,0 +1,18 @@
1
+ import React from 'react';
2
+
3
+ import type { JSX } from 'react';
4
+
5
+ import { ConsentBanner } from '@redocly/theme/components/Consent/ConsentBanner';
6
+ import { ConsentDialog } from '@redocly/theme/components/Consent/ConsentDialog';
7
+ import { useConsentTelemetry } from '@redocly/theme/core/hooks';
8
+
9
+ export function Consent(): JSX.Element {
10
+ useConsentTelemetry();
11
+
12
+ return (
13
+ <>
14
+ <ConsentBanner />
15
+ <ConsentDialog />
16
+ </>
17
+ );
18
+ }
@@ -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
+ `;
@@ -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
  );