@redocly/theme 0.60.0-next.6 → 0.60.0-next.7

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.
@@ -0,0 +1,6 @@
1
+ import type { JSX } from 'react';
2
+ type BannerProps = {
3
+ className?: string;
4
+ };
5
+ export declare function Banner({ className }: BannerProps): JSX.Element | null;
6
+ export {};
@@ -0,0 +1,172 @@
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
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.Banner = Banner;
37
+ const react_1 = __importStar(require("react"));
38
+ const styled_components_1 = __importStar(require("styled-components"));
39
+ const hooks_1 = require("../../core/hooks");
40
+ const Markdown_1 = require("../../components/Markdown/Markdown");
41
+ const CloseIcon_1 = require("../../icons/CloseIcon/CloseIcon");
42
+ const Button_1 = require("../../components/Button/Button");
43
+ function setBannerHeight(height) {
44
+ document.documentElement.style.setProperty('--banner-height', `${height}px`);
45
+ }
46
+ function Banner({ className }) {
47
+ const { useBanner, useMarkdownText } = (0, hooks_1.useThemeHooks)();
48
+ const { banner, dismissBanner } = useBanner();
49
+ const [displayBanner, setDisplayBanner] = (0, react_1.useState)(undefined);
50
+ const [isVisible, setIsVisible] = (0, react_1.useState)(false);
51
+ const markdownContent = useMarkdownText((displayBanner === null || displayBanner === void 0 ? void 0 : displayBanner.content) || '');
52
+ const bannerRef = (0, react_1.useRef)(null);
53
+ (0, react_1.useEffect)(() => {
54
+ if (banner) {
55
+ setDisplayBanner(banner);
56
+ requestAnimationFrame(() => {
57
+ requestAnimationFrame(() => {
58
+ setIsVisible(true);
59
+ });
60
+ });
61
+ }
62
+ else {
63
+ setIsVisible(false);
64
+ const timer = setTimeout(() => {
65
+ setDisplayBanner(undefined);
66
+ }, 400);
67
+ return () => clearTimeout(timer);
68
+ }
69
+ }, [banner]);
70
+ (0, react_1.useEffect)(() => {
71
+ if (!displayBanner) {
72
+ const timer = setTimeout(() => {
73
+ setBannerHeight(0);
74
+ }, 400);
75
+ return () => clearTimeout(timer);
76
+ }
77
+ const bannerElement = bannerRef.current;
78
+ if (!bannerElement)
79
+ return;
80
+ if (!isVisible) {
81
+ setBannerHeight(0);
82
+ return;
83
+ }
84
+ const updateHeight = () => {
85
+ const height = bannerElement.getBoundingClientRect().height;
86
+ setBannerHeight(height);
87
+ };
88
+ updateHeight();
89
+ const resizeObserver = new ResizeObserver(updateHeight);
90
+ resizeObserver.observe(bannerElement);
91
+ return () => {
92
+ resizeObserver.disconnect();
93
+ };
94
+ }, [displayBanner, isVisible]);
95
+ if (!displayBanner) {
96
+ return null;
97
+ }
98
+ const bannerColor = displayBanner.color || 'info';
99
+ return (react_1.default.createElement(BannerWrapper, { ref: bannerRef, "data-component-name": "Banner/Banner", className: className, "$color": bannerColor, "$isVisible": isVisible },
100
+ react_1.default.createElement(BannerContent, null,
101
+ react_1.default.createElement(Markdown_1.Markdown, { compact: true }, markdownContent)),
102
+ displayBanner.dismissible && (react_1.default.createElement(DismissButton, { variant: "ghost", size: "var(--banner-button-size)", icon: react_1.default.createElement(CloseIcon_1.CloseIcon, { color: `var(--banner-${bannerColor}-icon-color)`, size: "16px" }), onClick: () => dismissBanner(displayBanner.content), "aria-label": "Dismiss banner" }))));
103
+ }
104
+ const BannerContent = styled_components_1.default.div `
105
+ flex: 1;
106
+ display: flex;
107
+ align-items: center;
108
+ justify-content: center;
109
+
110
+ p {
111
+ margin: 0;
112
+ color: var(--banner-text-color);
113
+ text-align: center;
114
+
115
+ a:not([role='button']) {
116
+ color: var(--banner-link-color);
117
+ text-decoration: var(--banner-link-decoration);
118
+ }
119
+
120
+ [data-component-name='Button/Button'] {
121
+ --button-font-size: var(--banner-button-font-size);
122
+ --button-border-radius: var(--banner-button-border-radius);
123
+ --button-padding: var(--banner-button-padding-inline);
124
+ --button-line-height: var(--banner-button-line-height);
125
+ --button-icon-size: var(--banner-button-icon-size);
126
+ --button-icon-padding: var(--banner-button-icon-padding);
127
+ --button-icon-left-padding: var(--banner-button-icon-left-padding);
128
+ --button-icon-right-padding: var(--banner-button-icon-right-padding);
129
+ margin: var(--banner-button-margin);
130
+ }
131
+ }
132
+
133
+ p > * {
134
+ vertical-align: bottom;
135
+ }
136
+ `;
137
+ const BannerWrapper = styled_components_1.default.div `
138
+ display: flex;
139
+ align-items: center;
140
+ justify-content: space-between;
141
+ padding: var(--banner-padding);
142
+ color: var(--banner-text-color);
143
+ min-height: var(--banner-min-height);
144
+ position: absolute;
145
+ top: 0;
146
+ left: 0;
147
+ right: 0;
148
+ width: 100%;
149
+ z-index: var(--z-index-overlay);
150
+ transform: ${({ $isVisible }) => ($isVisible ? 'translateY(0)' : 'translateY(-100%)')};
151
+ transition: transform 0.4s ease-out;
152
+ ${({ $color }) => $color &&
153
+ (0, styled_components_1.css) `
154
+ background-color: var(--banner-${$color}-bg-color);
155
+
156
+ ${BannerContent} {
157
+ p {
158
+ color: var(--banner-${$color}-text-color);
159
+ }
160
+
161
+ a:not([role='button']) {
162
+ color: var(--banner-${$color}-link-color);
163
+ }
164
+ }
165
+ `}
166
+ `;
167
+ const DismissButton = (0, styled_components_1.default)(Button_1.Button) `
168
+ width: var(--banner-button-size);
169
+ height: var(--banner-button-size);
170
+ padding: var(--banner-button-padding);
171
+ `;
172
+ //# sourceMappingURL=Banner.js.map
@@ -0,0 +1 @@
1
+ export declare const banner: import("styled-components").FlattenSimpleInterpolation;
@@ -0,0 +1 @@
1
+ export declare const bannerDarkMode: import("styled-components").FlattenSimpleInterpolation;
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.bannerDarkMode = void 0;
4
+ const styled_components_1 = require("styled-components");
5
+ exports.bannerDarkMode = (0, styled_components_1.css) `
6
+ /**
7
+ * @tokens Banner
8
+ */
9
+
10
+ --banner-warning-text-color: var(--color-white); // @presenter Color
11
+ `;
12
+ //# sourceMappingURL=variables.dark.js.map
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.banner = void 0;
4
+ const styled_components_1 = require("styled-components");
5
+ exports.banner = (0, styled_components_1.css) `
6
+ /**
7
+ * @tokens Banner
8
+ */
9
+ --banner-button-size: 22px;
10
+ --banner-button-padding: 3px; // @presenter Spacing
11
+ --banner-button-font-size: var(--font-size-base);
12
+ --banner-button-border-radius: var(--border-radius);
13
+ --banner-button-padding-inline: 1px var(--spacing-sm);
14
+ --banner-button-line-height: var(--line-height-base); // @presenter LineHeight
15
+ --banner-button-icon-size: 14px;
16
+ --banner-button-icon-padding: 5px; // @presenter Spacing
17
+ --banner-button-icon-left-padding: 1px var(--spacing-sm) 1px 10px; // @presenter Spacing
18
+ --banner-button-icon-right-padding: 1px 10px 1px var(--spacing-sm); // @presenter Spacing
19
+ --banner-button-margin: 0 var(--spacing-xs); // @presenter Spacing
20
+ --banner-padding: var(--spacing-xs); // @presenter Spacing
21
+ --banner-link-decoration: var(--link-decoration-hover);
22
+ --banner-min-height: 38px;
23
+ --banner-gap: var(--spacing-xxs); // @presenter Spacing
24
+
25
+ --banner-info-bg-color: var(--color-info-base); // @presenter Color
26
+ --banner-info-text-color: var(--color-static-white); // @presenter Color
27
+ --banner-info-icon-color: var(--color-static-white); // @presenter Color
28
+ --banner-info-link-color: var(--banner-info-text-color); // @presenter Color
29
+
30
+ --banner-success-bg-color: var(--color-success-base); // @presenter Color
31
+ --banner-success-text-color: var(--color-static-white); // @presenter Color
32
+ --banner-success-icon-color: var(--color-static-white); // @presenter Color
33
+ --banner-success-link-color: var(--banner-success-text-color); // @presenter Color
34
+
35
+ --banner-warning-bg-color: var(--color-warning-base); // @presenter Color
36
+ --banner-warning-text-color: var(--color-black); // @presenter Color
37
+ --banner-warning-icon-color: var(--color-black); // @presenter Color
38
+ --banner-warning-link-color: var(--banner-warning-text-color); // @presenter Color
39
+
40
+ --banner-error-bg-color: var(--color-error-base); // @presenter Color
41
+ --banner-error-text-color: var(--color-static-white); // @presenter Color
42
+ --banner-error-icon-color: var(--color-static-white); // @presenter Color
43
+ --banner-error-link-color: var(--banner-error-text-color); // @presenter Color
44
+ `;
45
+ //# sourceMappingURL=variables.js.map
@@ -40,6 +40,7 @@ const LanguageDropdown = (0, styled_components_1.default)(Dropdown_1.Dropdown).a
40
40
  },
41
41
  })) `
42
42
  display: none;
43
+ height: auto;
43
44
  @media screen and (min-width: ${utils_1.breakpoints.medium}) {
44
45
  display: block;
45
46
  }
@@ -19,6 +19,7 @@ const ProductPicker_1 = require("../../components/Product/ProductPicker");
19
19
  const Button_1 = require("../../components/Button/Button");
20
20
  const MenuIcon_1 = require("../../icons/MenuIcon/MenuIcon");
21
21
  const CloseIcon_1 = require("../../icons/CloseIcon/CloseIcon");
22
+ const Banner_1 = require("../../components/Banner/Banner");
22
23
  function Navbar({ className }) {
23
24
  var _a;
24
25
  const { isOpen, closeMobileMenu, openMobileMenu } = (0, hooks_1.useMobileMenu)(false);
@@ -34,6 +35,7 @@ function Navbar({ className }) {
34
35
  const hideSearch = (searchSettings === null || searchSettings === void 0 ? void 0 : searchSettings.hide) || ((searchSettings === null || searchSettings === void 0 ? void 0 : searchSettings.placement) && (searchSettings === null || searchSettings === void 0 ? void 0 : searchSettings.placement) !== 'navbar');
35
36
  const hideUserMenu = userMenuSettings === null || userMenuSettings === void 0 ? void 0 : userMenuSettings.hide;
36
37
  return (react_1.default.createElement(NavbarWrapper, { "data-component-name": "Navbar/Navbar", className: className },
38
+ react_1.default.createElement(Banner_1.Banner, null),
37
39
  isOpen && react_1.default.createElement(MenuMobile_1.MenuMobile, { hideUserProfile: !!hideUserMenu }),
38
40
  react_1.default.createElement(NavbarRow, null,
39
41
  logo && react_1.default.createElement(NavbarLogo_1.NavbarLogo, { config: logo }),
@@ -55,16 +57,15 @@ function Navbar({ className }) {
55
57
  }
56
58
  const NavbarWrapper = styled_components_1.default.nav `
57
59
  --text-color: var(--navbar-text-color);
60
+ height: calc(var(--navbar-height) + var(--banner-height));
61
+ transition: height 0.4s ease-out;
58
62
 
59
63
  position: sticky;
60
- display: flex;
61
64
  top: 0;
62
- height: var(--navbar-height);
65
+ display: flex;
66
+ flex-direction: column;
63
67
  flex-shrink: 0;
64
- align-items: center;
65
68
  box-sizing: border-box;
66
- padding: var(--navbar-padding);
67
- border: var(--navbar-border);
68
69
  font-size: var(--navbar-font-size);
69
70
  font-family: var(--navbar-font-family);
70
71
  z-index: var(--z-index-raised);
@@ -88,7 +89,14 @@ const NavbarRow = styled_components_1.default.div `
88
89
  justify-content: space-between;
89
90
  width: 100%;
90
91
  gap: 8px;
92
+ height: var(--navbar-height);
91
93
  max-width: var(--navbar-container-max-width);
94
+ padding: var(--navbar-padding);
95
+ border: var(--navbar-border);
96
+ background: var(--navbar-bg-color);
97
+ box-sizing: border-box;
98
+ margin-top: var(--banner-height);
99
+ transition: margin-top 0.5s ease-out;
92
100
 
93
101
  @media screen and (min-width: ${utils_1.breakpoints.max}) {
94
102
  max-width: var(--container-max-width);
@@ -15,6 +15,7 @@ const fallbacks = {
15
15
  **/
16
16
  useOtelTelemetry: () => ({ send: () => { } }),
17
17
  useBreadcrumbs: () => ({ breadcrumbs: [], siblings: undefined }),
18
+ useBanner: () => ({ banner: undefined, dismissBanner: () => { } }),
18
19
  useCodeHighlight: () => ({ highlight: (rawContent) => rawContent }),
19
20
  useUserMenu: () => ({}),
20
21
  usePageData: () => null,
@@ -16,6 +16,7 @@ const variables_dark_11 = require("../../markdoc/components/Cards/variables.dark
16
16
  const variables_dark_12 = require("../../components/Catalog/variables.dark");
17
17
  const variables_dark_13 = require("../../components/PageActions/variables.dark");
18
18
  const variables_dark_14 = require("../../components/Tooltip/variables.dark");
19
+ const variables_dark_15 = require("../../components/Banner/variables.dark");
19
20
  const replayDarkMode = (0, styled_components_1.css) `
20
21
  /**
21
22
  * @tokens Replay Colors
@@ -327,6 +328,7 @@ exports.darkMode = (0, styled_components_1.css) `
327
328
  ${variables_dark_12.catalogDarkMode}
328
329
  ${variables_dark_13.pageActionsDarkMode}
329
330
  ${variables_dark_14.tooltipDarkMode}
331
+ ${variables_dark_15.bannerDarkMode}
330
332
 
331
333
  /**
332
334
  * @tokens Dark Theme Scrollbar Config
@@ -28,21 +28,22 @@ const variables_23 = require("../../components/Menu/variables");
28
28
  const variables_24 = require("../../components/CodeBlock/variables");
29
29
  const variables_25 = require("../../components/Product/variables");
30
30
  const variables_26 = require("../../components/Markdown/variables");
31
- const variables_27 = require("../../markdoc/components/Tabs/variables");
32
- const variables_28 = require("../../markdoc/components/Mermaid/variables");
33
- const variables_29 = require("../../components/LastUpdated/variables");
34
- const variables_30 = require("../../components/Logo/variables");
35
- const variables_31 = require("../../components/StatusCode/variables");
36
- const variables_32 = require("../../components/Segmented/variables");
37
- const variables_33 = require("../../components/UserMenu/variables");
38
- const variables_34 = require("../../components/Tags/variables");
39
- const variables_35 = require("../../components/VersionPicker/variables");
40
- const variables_36 = require("../../components/DatePicker/variables");
41
- const variables_37 = require("../../components/Switch/variables");
42
- const variables_38 = require("../../markdoc/components/Cards/variables");
43
- const variables_39 = require("../../markdoc/components/CodeWalkthrough/variables");
44
- const variables_40 = require("../../components/SkipContent/variables");
45
- const variables_41 = require("../../components/PageActions/variables");
31
+ const variables_27 = require("../../components/Banner/variables");
32
+ const variables_28 = require("../../markdoc/components/Tabs/variables");
33
+ const variables_29 = require("../../markdoc/components/Mermaid/variables");
34
+ const variables_30 = require("../../components/LastUpdated/variables");
35
+ const variables_31 = require("../../components/Logo/variables");
36
+ const variables_32 = require("../../components/StatusCode/variables");
37
+ const variables_33 = require("../../components/Segmented/variables");
38
+ const variables_34 = require("../../components/UserMenu/variables");
39
+ const variables_35 = require("../../components/Tags/variables");
40
+ const variables_36 = require("../../components/VersionPicker/variables");
41
+ const variables_37 = require("../../components/DatePicker/variables");
42
+ const variables_38 = require("../../components/Switch/variables");
43
+ const variables_39 = require("../../markdoc/components/Cards/variables");
44
+ const variables_40 = require("../../markdoc/components/CodeWalkthrough/variables");
45
+ const variables_41 = require("../../components/SkipContent/variables");
46
+ const variables_42 = require("../../components/PageActions/variables");
46
47
  const dark_1 = require("./dark");
47
48
  const themeColors = (0, styled_components_1.css) `
48
49
  /* === Palette === */
@@ -1216,32 +1217,33 @@ exports.styles = (0, styled_components_1.css) `
1216
1217
  ${apiReferenceDocs}
1217
1218
  ${variables_11.apiReferencePanels}
1218
1219
  ${badges}
1220
+ ${variables_27.banner}
1219
1221
  ${borders}
1220
1222
  ${variables_5.breadcrumbs}
1221
1223
  ${variables_19.button}
1222
1224
  ${variables_20.aiAssistantButton}
1223
1225
  ${variables_20.connectMCPButton}
1224
- ${variables_38.cards}
1226
+ ${variables_39.cards}
1225
1227
  ${variables_8.catalog}
1226
1228
  ${variables_10.catalogClassic}
1227
1229
  ${variables_24.code}
1228
- ${variables_39.codeWalkthrough}
1230
+ ${variables_40.codeWalkthrough}
1229
1231
  ${docsDropdown}
1230
1232
  ${variables_14.dropdown}
1231
1233
  ${error}
1232
1234
  ${variables_9.filter}
1233
1235
  ${variables_18.footer}
1234
1236
  ${headingsTypography}
1235
- ${variables_34.httpTag}
1237
+ ${variables_35.httpTag}
1236
1238
  ${inputs}
1237
1239
  ${variables_1.languagePicker}
1238
- ${variables_29.lastUpdated}
1240
+ ${variables_30.lastUpdated}
1239
1241
  ${links}
1240
1242
  ${loadProgressBar}
1241
- ${variables_30.logo}
1243
+ ${variables_31.logo}
1242
1244
  ${variables_26.markdown}
1243
- ${variables_27.markdownTabs}
1244
- ${variables_28.mermaid}
1245
+ ${variables_28.markdownTabs}
1246
+ ${variables_29.mermaid}
1245
1247
  ${variables_23.menu}
1246
1248
  ${variables_23.mobileMenu}
1247
1249
  ${modal}
@@ -1259,23 +1261,23 @@ exports.styles = (0, styled_components_1.css) `
1259
1261
  ${variables_7.toc}
1260
1262
  ${variables_15.tooltip}
1261
1263
  ${typography}
1262
- ${variables_33.userMenu}
1263
- ${variables_35.versionPicker}
1264
+ ${variables_34.userMenu}
1265
+ ${variables_36.versionPicker}
1264
1266
  ${zIndexDepth}
1265
1267
  ${scorecardColors}
1266
- ${variables_31.statusCode}
1268
+ ${variables_32.statusCode}
1267
1269
  ${tab}
1268
1270
  ${icon}
1269
1271
  ${tree}
1270
- ${variables_32.segmented}
1271
- ${variables_37.switcher}
1272
+ ${variables_33.segmented}
1273
+ ${variables_38.switcher}
1272
1274
  ${variables_16.checkbox}
1273
1275
  ${variables_3.feedback}
1274
1276
  ${variables_2.scorecard}
1275
- ${variables_36.datePicker}
1277
+ ${variables_37.datePicker}
1276
1278
  ${replay}
1277
- ${variables_40.skipContent}
1278
- ${variables_41.pageActions}
1279
+ ${variables_41.skipContent}
1280
+ ${variables_42.pageActions}
1279
1281
 
1280
1282
  background-color: var(--bg-color);
1281
1283
  color: var(--text-color-primary);
@@ -1,5 +1,5 @@
1
1
  import type { AsyncApiRealmUI } from '@redocly/realm-asyncapi-sdk';
2
- import type { CatalogEntityConfig, PageData, PageProps, ResolvedNavItemWithLink, Version } from '@redocly/config';
2
+ import type { BannerConfig, CatalogEntityConfig, PageData, PageProps, ResolvedNavItemWithLink, Version } from '@redocly/config';
3
3
  import type { ShikiTransformer } from '@shikijs/types';
4
4
  import type { Callback, TFunction as TFunc } from 'i18next';
5
5
  import type { To, Location, NavigateFunction } from 'react-router-dom';
@@ -60,6 +60,10 @@ export type ThemeHooks = {
60
60
  breadcrumbs: BreadcrumbItem[];
61
61
  currentItemSiblings?: BreadcrumbItem[];
62
62
  };
63
+ useBanner: () => {
64
+ banner: BannerConfig | undefined;
65
+ dismissBanner: (content: string) => void;
66
+ };
63
67
  useSearch: (product?: string, autoSearchDisabled?: boolean, searchSessionId?: string) => {
64
68
  query: string;
65
69
  setQuery: React.Dispatch<React.SetStateAction<string>>;
package/lib/index.d.ts CHANGED
@@ -44,6 +44,7 @@ export * from './components/Footer/Footer';
44
44
  export * from './components/Footer/FooterColumn';
45
45
  export * from './components/Footer/FooterCopyright';
46
46
  export * from './components/Footer/FooterItem';
47
+ export * from './components/Banner/Banner';
47
48
  export * from './components/Typography/CompactTypography';
48
49
  export * from './components/Typography/Emphasis';
49
50
  export * from './components/Typography/H1';
package/lib/index.js CHANGED
@@ -89,6 +89,8 @@ __exportStar(require("./components/Footer/Footer"), exports);
89
89
  __exportStar(require("./components/Footer/FooterColumn"), exports);
90
90
  __exportStar(require("./components/Footer/FooterCopyright"), exports);
91
91
  __exportStar(require("./components/Footer/FooterItem"), exports);
92
+ /* Banner */
93
+ __exportStar(require("./components/Banner/Banner"), exports);
92
94
  /* Typography */
93
95
  __exportStar(require("./components/Typography/CompactTypography"), exports);
94
96
  __exportStar(require("./components/Typography/Emphasis"), exports);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@redocly/theme",
3
- "version": "0.60.0-next.6",
3
+ "version": "0.60.0-next.7",
4
4
  "description": "Shared UI components lib",
5
5
  "keywords": [
6
6
  "theme",
@@ -0,0 +1,179 @@
1
+ import React, { useRef, useEffect, useState } from 'react';
2
+ import styled, { css } from 'styled-components';
3
+
4
+ import type { BannerConfig } from '@redocly/config';
5
+ import type { JSX } from 'react';
6
+
7
+ import { useThemeHooks } from '@redocly/theme/core/hooks';
8
+ import { Markdown } from '@redocly/theme/components/Markdown/Markdown';
9
+ import { CloseIcon } from '@redocly/theme/icons/CloseIcon/CloseIcon';
10
+ import { Button } from '@redocly/theme/components/Button/Button';
11
+
12
+ type BannerColor = 'info' | 'success' | 'warning' | 'error';
13
+
14
+ type BannerProps = {
15
+ className?: string;
16
+ };
17
+
18
+ function setBannerHeight(height: number): void {
19
+ document.documentElement.style.setProperty('--banner-height', `${height}px`);
20
+ }
21
+
22
+ export function Banner({ className }: BannerProps): JSX.Element | null {
23
+ const { useBanner, useMarkdownText } = useThemeHooks();
24
+ const { banner, dismissBanner } = useBanner();
25
+ const [displayBanner, setDisplayBanner] = useState<BannerConfig | undefined>(undefined);
26
+ const [isVisible, setIsVisible] = useState(false);
27
+ const markdownContent = useMarkdownText(displayBanner?.content || '');
28
+ const bannerRef = useRef<HTMLDivElement>(null);
29
+
30
+ useEffect(() => {
31
+ if (banner) {
32
+ setDisplayBanner(banner);
33
+ requestAnimationFrame(() => {
34
+ requestAnimationFrame(() => {
35
+ setIsVisible(true);
36
+ });
37
+ });
38
+ } else {
39
+ setIsVisible(false);
40
+ const timer = setTimeout(() => {
41
+ setDisplayBanner(undefined);
42
+ }, 400);
43
+ return () => clearTimeout(timer);
44
+ }
45
+ }, [banner]);
46
+
47
+ useEffect(() => {
48
+ if (!displayBanner) {
49
+ const timer = setTimeout(() => {
50
+ setBannerHeight(0);
51
+ }, 400);
52
+ return () => clearTimeout(timer);
53
+ }
54
+
55
+ const bannerElement = bannerRef.current;
56
+ if (!bannerElement) return;
57
+
58
+ if (!isVisible) {
59
+ setBannerHeight(0);
60
+ return;
61
+ }
62
+
63
+ const updateHeight = (): void => {
64
+ const height = bannerElement.getBoundingClientRect().height;
65
+ setBannerHeight(height);
66
+ };
67
+
68
+ updateHeight();
69
+
70
+ const resizeObserver = new ResizeObserver(updateHeight);
71
+ resizeObserver.observe(bannerElement);
72
+
73
+ return () => {
74
+ resizeObserver.disconnect();
75
+ };
76
+ }, [displayBanner, isVisible]);
77
+
78
+ if (!displayBanner) {
79
+ return null;
80
+ }
81
+
82
+ const bannerColor =
83
+ ((displayBanner as BannerConfig & { color?: BannerColor }).color as BannerColor) || 'info';
84
+
85
+ return (
86
+ <BannerWrapper
87
+ ref={bannerRef}
88
+ data-component-name="Banner/Banner"
89
+ className={className}
90
+ $color={bannerColor}
91
+ $isVisible={isVisible}
92
+ >
93
+ <BannerContent>
94
+ <Markdown compact>{markdownContent}</Markdown>
95
+ </BannerContent>
96
+ {displayBanner.dismissible && (
97
+ <DismissButton
98
+ variant="ghost"
99
+ size="var(--banner-button-size)"
100
+ icon={<CloseIcon color={`var(--banner-${bannerColor}-icon-color)`} size="16px" />}
101
+ onClick={() => dismissBanner(displayBanner.content)}
102
+ aria-label="Dismiss banner"
103
+ />
104
+ )}
105
+ </BannerWrapper>
106
+ );
107
+ }
108
+
109
+ const BannerContent = styled.div`
110
+ flex: 1;
111
+ display: flex;
112
+ align-items: center;
113
+ justify-content: center;
114
+
115
+ p {
116
+ margin: 0;
117
+ color: var(--banner-text-color);
118
+ text-align: center;
119
+
120
+ a:not([role='button']) {
121
+ color: var(--banner-link-color);
122
+ text-decoration: var(--banner-link-decoration);
123
+ }
124
+
125
+ [data-component-name='Button/Button'] {
126
+ --button-font-size: var(--banner-button-font-size);
127
+ --button-border-radius: var(--banner-button-border-radius);
128
+ --button-padding: var(--banner-button-padding-inline);
129
+ --button-line-height: var(--banner-button-line-height);
130
+ --button-icon-size: var(--banner-button-icon-size);
131
+ --button-icon-padding: var(--banner-button-icon-padding);
132
+ --button-icon-left-padding: var(--banner-button-icon-left-padding);
133
+ --button-icon-right-padding: var(--banner-button-icon-right-padding);
134
+ margin: var(--banner-button-margin);
135
+ }
136
+ }
137
+
138
+ p > * {
139
+ vertical-align: bottom;
140
+ }
141
+ `;
142
+
143
+ const BannerWrapper = styled.div<{ $color?: BannerColor; $isVisible?: boolean }>`
144
+ display: flex;
145
+ align-items: center;
146
+ justify-content: space-between;
147
+ padding: var(--banner-padding);
148
+ color: var(--banner-text-color);
149
+ min-height: var(--banner-min-height);
150
+ position: absolute;
151
+ top: 0;
152
+ left: 0;
153
+ right: 0;
154
+ width: 100%;
155
+ z-index: var(--z-index-overlay);
156
+ transform: ${({ $isVisible }) => ($isVisible ? 'translateY(0)' : 'translateY(-100%)')};
157
+ transition: transform 0.4s ease-out;
158
+ ${({ $color }) =>
159
+ $color &&
160
+ css`
161
+ background-color: var(--banner-${$color}-bg-color);
162
+
163
+ ${BannerContent} {
164
+ p {
165
+ color: var(--banner-${$color}-text-color);
166
+ }
167
+
168
+ a:not([role='button']) {
169
+ color: var(--banner-${$color}-link-color);
170
+ }
171
+ }
172
+ `}
173
+ `;
174
+
175
+ const DismissButton = styled(Button)`
176
+ width: var(--banner-button-size);
177
+ height: var(--banner-button-size);
178
+ padding: var(--banner-button-padding);
179
+ `;
@@ -0,0 +1,10 @@
1
+ import { css } from 'styled-components';
2
+
3
+ export const bannerDarkMode = css`
4
+ /**
5
+ * @tokens Banner
6
+ */
7
+
8
+ --banner-warning-text-color: var(--color-white); // @presenter Color
9
+ `;
10
+
@@ -0,0 +1,43 @@
1
+ import { css } from 'styled-components';
2
+
3
+ export const banner = css`
4
+ /**
5
+ * @tokens Banner
6
+ */
7
+ --banner-button-size: 22px;
8
+ --banner-button-padding: 3px; // @presenter Spacing
9
+ --banner-button-font-size: var(--font-size-base);
10
+ --banner-button-border-radius: var(--border-radius);
11
+ --banner-button-padding-inline: 1px var(--spacing-sm);
12
+ --banner-button-line-height: var(--line-height-base); // @presenter LineHeight
13
+ --banner-button-icon-size: 14px;
14
+ --banner-button-icon-padding: 5px; // @presenter Spacing
15
+ --banner-button-icon-left-padding: 1px var(--spacing-sm) 1px 10px; // @presenter Spacing
16
+ --banner-button-icon-right-padding: 1px 10px 1px var(--spacing-sm); // @presenter Spacing
17
+ --banner-button-margin: 0 var(--spacing-xs); // @presenter Spacing
18
+ --banner-padding: var(--spacing-xs); // @presenter Spacing
19
+ --banner-link-decoration: var(--link-decoration-hover);
20
+ --banner-min-height: 38px;
21
+ --banner-gap: var(--spacing-xxs); // @presenter Spacing
22
+
23
+ --banner-info-bg-color: var(--color-info-base); // @presenter Color
24
+ --banner-info-text-color: var(--color-static-white); // @presenter Color
25
+ --banner-info-icon-color: var(--color-static-white); // @presenter Color
26
+ --banner-info-link-color: var(--banner-info-text-color); // @presenter Color
27
+
28
+ --banner-success-bg-color: var(--color-success-base); // @presenter Color
29
+ --banner-success-text-color: var(--color-static-white); // @presenter Color
30
+ --banner-success-icon-color: var(--color-static-white); // @presenter Color
31
+ --banner-success-link-color: var(--banner-success-text-color); // @presenter Color
32
+
33
+ --banner-warning-bg-color: var(--color-warning-base); // @presenter Color
34
+ --banner-warning-text-color: var(--color-black); // @presenter Color
35
+ --banner-warning-icon-color: var(--color-black); // @presenter Color
36
+ --banner-warning-link-color: var(--banner-warning-text-color); // @presenter Color
37
+
38
+ --banner-error-bg-color: var(--color-error-base); // @presenter Color
39
+ --banner-error-text-color: var(--color-static-white); // @presenter Color
40
+ --banner-error-icon-color: var(--color-static-white); // @presenter Color
41
+ --banner-error-link-color: var(--banner-error-text-color); // @presenter Color
42
+ `;
43
+
@@ -64,6 +64,7 @@ const LanguageDropdown = styled(Dropdown).attrs(() => ({
64
64
  },
65
65
  }))`
66
66
  display: none;
67
+ height: auto;
67
68
  @media screen and (min-width: ${breakpoints.medium}) {
68
69
  display: block;
69
70
  }
@@ -17,6 +17,7 @@ import { ProductPicker } from '@redocly/theme/components/Product/ProductPicker';
17
17
  import { Button } from '@redocly/theme/components/Button/Button';
18
18
  import { MenuIcon } from '@redocly/theme/icons/MenuIcon/MenuIcon';
19
19
  import { CloseIcon } from '@redocly/theme/icons/CloseIcon/CloseIcon';
20
+ import { Banner } from '@redocly/theme/components/Banner/Banner';
20
21
 
21
22
  export type NavbarProps = {
22
23
  className?: string;
@@ -43,6 +44,7 @@ export function Navbar({ className }: NavbarProps): JSX.Element | null {
43
44
 
44
45
  return (
45
46
  <NavbarWrapper data-component-name="Navbar/Navbar" className={className}>
47
+ <Banner />
46
48
  {isOpen && <MenuMobile hideUserProfile={!!hideUserMenu} />}
47
49
  <NavbarRow>
48
50
  {logo && <NavbarLogo config={logo} />}
@@ -76,16 +78,15 @@ export function Navbar({ className }: NavbarProps): JSX.Element | null {
76
78
 
77
79
  const NavbarWrapper = styled.nav`
78
80
  --text-color: var(--navbar-text-color);
81
+ height: calc(var(--navbar-height) + var(--banner-height));
82
+ transition: height 0.4s ease-out;
79
83
 
80
84
  position: sticky;
81
- display: flex;
82
85
  top: 0;
83
- height: var(--navbar-height);
86
+ display: flex;
87
+ flex-direction: column;
84
88
  flex-shrink: 0;
85
- align-items: center;
86
89
  box-sizing: border-box;
87
- padding: var(--navbar-padding);
88
- border: var(--navbar-border);
89
90
  font-size: var(--navbar-font-size);
90
91
  font-family: var(--navbar-font-family);
91
92
  z-index: var(--z-index-raised);
@@ -110,7 +111,14 @@ const NavbarRow = styled.div`
110
111
  justify-content: space-between;
111
112
  width: 100%;
112
113
  gap: 8px;
114
+ height: var(--navbar-height);
113
115
  max-width: var(--navbar-container-max-width);
116
+ padding: var(--navbar-padding);
117
+ border: var(--navbar-border);
118
+ background: var(--navbar-bg-color);
119
+ box-sizing: border-box;
120
+ margin-top: var(--banner-height);
121
+ transition: margin-top 0.5s ease-out;
114
122
 
115
123
  @media screen and (min-width: ${breakpoints.max}) {
116
124
  max-width: var(--container-max-width);
@@ -17,6 +17,10 @@ export const useThemeHooks = vi.fn(() => ({
17
17
  sendCodeSnippetReportedMessage: vi.fn(),
18
18
  })),
19
19
  useBreadcrumbs: vi.fn().mockReturnValue({ breadcrumbs: [], siblings: undefined }),
20
+ useBanner: vi.fn(() => ({
21
+ banner: undefined,
22
+ dismissBanner: vi.fn(),
23
+ })),
20
24
  usePageSharedData: vi.fn().mockReturnValue({}),
21
25
  useCatalogClassic: vi.fn(() => ({
22
26
  groups: [],
@@ -17,6 +17,7 @@ const fallbacks = {
17
17
  **/
18
18
  useOtelTelemetry: () => ({ send: () => {} }),
19
19
  useBreadcrumbs: () => ({ breadcrumbs: [], siblings: undefined }),
20
+ useBanner: () => ({ banner: undefined, dismissBanner: () => {} }),
20
21
  useCodeHighlight: () => ({ highlight: (rawContent: string) => rawContent }),
21
22
  useUserMenu: () => ({}),
22
23
  usePageData: () => null,
@@ -14,6 +14,7 @@ import { cardsDarkMode } from '@redocly/theme/markdoc/components/Cards/variables
14
14
  import { catalogDarkMode } from '@redocly/theme/components/Catalog/variables.dark';
15
15
  import { pageActionsDarkMode } from '@redocly/theme/components/PageActions/variables.dark';
16
16
  import { tooltipDarkMode } from '@redocly/theme/components/Tooltip/variables.dark';
17
+ import { bannerDarkMode } from '@redocly/theme/components/Banner/variables.dark';
17
18
 
18
19
  const replayDarkMode = css`
19
20
  /**
@@ -328,6 +329,7 @@ export const darkMode = css`
328
329
  ${catalogDarkMode}
329
330
  ${pageActionsDarkMode}
330
331
  ${tooltipDarkMode}
332
+ ${bannerDarkMode}
331
333
 
332
334
  /**
333
335
  * @tokens Dark Theme Scrollbar Config
@@ -26,6 +26,7 @@ import { menu, mobileMenu } from '@redocly/theme/components/Menu/variables';
26
26
  import { code } from '@redocly/theme/components/CodeBlock/variables';
27
27
  import { productPicker } from '@redocly/theme/components/Product/variables';
28
28
  import { markdown } from '@redocly/theme/components/Markdown/variables';
29
+ import { banner } from '@redocly/theme/components/Banner/variables';
29
30
  import { markdownTabs } from '@redocly/theme/markdoc/components/Tabs/variables';
30
31
  import { mermaid } from '@redocly/theme/markdoc/components/Mermaid/variables';
31
32
  import { lastUpdated } from '@redocly/theme/components/LastUpdated/variables';
@@ -1239,6 +1240,7 @@ export const styles = css`
1239
1240
  ${apiReferenceDocs}
1240
1241
  ${apiReferencePanels}
1241
1242
  ${badges}
1243
+ ${banner}
1242
1244
  ${borders}
1243
1245
  ${breadcrumbs}
1244
1246
  ${button}
@@ -1,5 +1,6 @@
1
1
  import type { AsyncApiRealmUI } from '@redocly/realm-asyncapi-sdk';
2
2
  import type {
3
+ BannerConfig,
3
4
  CatalogEntityConfig,
4
5
  PageData,
5
6
  PageProps,
@@ -83,6 +84,10 @@ export type ThemeHooks = {
83
84
  breadcrumbs: BreadcrumbItem[];
84
85
  currentItemSiblings?: BreadcrumbItem[];
85
86
  };
87
+ useBanner: () => {
88
+ banner: BannerConfig | undefined;
89
+ dismissBanner: (content: string) => void;
90
+ };
86
91
  useSearch: (
87
92
  product?: string,
88
93
  autoSearchDisabled?: boolean,
package/src/index.ts CHANGED
@@ -52,6 +52,8 @@ export * from '@redocly/theme/components/Footer/Footer';
52
52
  export * from '@redocly/theme/components/Footer/FooterColumn';
53
53
  export * from '@redocly/theme/components/Footer/FooterCopyright';
54
54
  export * from '@redocly/theme/components/Footer/FooterItem';
55
+ /* Banner */
56
+ export * from '@redocly/theme/components/Banner/Banner';
55
57
  /* Typography */
56
58
  export * from '@redocly/theme/components/Typography/CompactTypography';
57
59
  export * from '@redocly/theme/components/Typography/Emphasis';