diff --git a/package.json b/package.json
index 102e896..ccb1bf9 100644
--- a/package.json
+++ b/package.json
@@ -6,7 +6,7 @@
"@auth0/auth0-react": "1.10.2",
"@emotion/react": "11.7.1",
"@emotion/styled": "11.6.0",
- "@kingsrook/qqq-frontend-core": "1.0.114",
+ "@kingsrook/qqq-frontend-core": "1.0.117",
"@mui/icons-material": "5.4.1",
"@mui/material": "5.11.1",
"@mui/styles": "5.11.1",
diff --git a/pom.xml b/pom.xml
index 33d28d1..94f876b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -66,7 +66,7 @@
com.kingsrook.qqq
qqq-backend-core
- 0.21.0
+ 0.25.0-integration-sprint-62-20250307-205536
org.slf4j
diff --git a/src/App.tsx b/src/App.tsx
index fd4c6b5..62ca7a2 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -29,6 +29,7 @@ import {QInstance} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QInstan
import {QProcessMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QProcessMetaData";
import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
import Avatar from "@mui/material/Avatar";
+import Box from "@mui/material/Box";
import CssBaseline from "@mui/material/CssBaseline";
import Icon from "@mui/material/Icon";
import {ThemeProvider} from "@mui/material/styles";
@@ -38,6 +39,7 @@ import jwt_decode from "jwt-decode";
import QContext from "QContext";
import Sidenav from "qqq/components/horseshoe/sidenav/SideNav";
import theme from "qqq/components/legacy/Theme";
+import {getBannerClassName, getBannerStyles, getBanner, makeBannerContent} from "qqq/components/misc/Banners";
import {setMiniSidenav, setOpenConfigurator, useMaterialUIController} from "qqq/context";
import AppHome from "qqq/pages/apps/Home";
import NoApps from "qqq/pages/apps/NoApps";
@@ -691,6 +693,23 @@ export default function App()
}
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ function banner(): JSX.Element | null
+ {
+ const banner = getBanner(metaData?.branding, "QFMD_TOP_OF_SITE");
+
+ if (!banner)
+ {
+ return (null);
+ }
+
+ return (
+ {makeBannerContent(banner)}
+ );
+ }
+
return (
appRoutes && (
@@ -718,6 +737,7 @@ export default function App()
+ {banner()}
.
+ */
+
+package com.kingsrook.qqq.frontend.materialdashboard.model.metadata;
+
+
+import com.kingsrook.qqq.backend.core.model.metadata.branding.BannerSlot;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+public enum MaterialDashboardBannerSlots implements BannerSlot
+{
+ QFMD_TOP_OF_SITE,
+ QFMD_TOP_OF_BODY,
+ QFMD_SIDE_NAV_UNDER_LOGO
+}
diff --git a/src/qqq/components/horseshoe/sidenav/SideNav.tsx b/src/qqq/components/horseshoe/sidenav/SideNav.tsx
index 8ac14e9..da14699 100644
--- a/src/qqq/components/horseshoe/sidenav/SideNav.tsx
+++ b/src/qqq/components/horseshoe/sidenav/SideNav.tsx
@@ -34,6 +34,7 @@ import SideNavList from "qqq/components/horseshoe/sidenav/SideNavList";
import SidenavRoot from "qqq/components/horseshoe/sidenav/SideNavRoot";
import sidenavLogoLabel from "qqq/components/horseshoe/sidenav/styles/SideNav";
import MDTypography from "qqq/components/legacy/MDTypography";
+import {getBannerClassName, getBannerStyles, getBanner, makeBannerContent} from "qqq/components/misc/Banners";
import {setMiniSidenav, setTransparentSidenav, setWhiteSidenav, useMaterialUIController,} from "qqq/context";
@@ -300,6 +301,30 @@ function Sidenav({color, icon, logo, appName, branding, routes, ...rest}: Props)
}
);
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ function EnvironmentBanner({branding}: { branding: QBrandingMetaData }): JSX.Element | null
+ {
+ // deprecated!
+ if (branding && branding.environmentBannerText)
+ {
+ return
+ {branding.environmentBannerText}
+ ;
+ }
+
+ const banner = getBanner(branding, "QFMD_SIDE_NAV_UNDER_LOGO");
+ if (banner)
+ {
+ return
+ {makeBannerContent(banner)}
+ ;
+ }
+
+ return (null);
+ }
+
return (
}
- {
- branding && branding.environmentBannerText &&
-
- {branding.environmentBannerText}
-
- }
+
.
+ */
+
+import {Banner} from "@kingsrook/qqq-frontend-core/lib/model/metaData/Banner";
+import {QBrandingMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QBrandingMetaData";
+import parse from "html-react-parser";
+
+/////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// On may render a banner using the functions in this file as: //
+// //
+// const banner = getBanner(branding, "QFMD_SIDE_NAV_UNDER_LOGO"); //
+// return ( //
+// {makeBannerContent(banner)} //
+// ); //
+/////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+
+/***************************************************************************
+ **
+ ***************************************************************************/
+export function getBanner(branding: QBrandingMetaData, slot: string): Banner | null
+{
+ if (branding?.banners?.has(slot))
+ {
+ return (branding.banners.get(slot));
+ }
+
+ return (null);
+}
+
+
+/***************************************************************************
+ **
+ ***************************************************************************/
+export function getBannerStyles(banner: Banner)
+{
+ let bgColor = "";
+ let color = "";
+
+ if (banner)
+ {
+ if (banner.backgroundColor)
+ {
+ bgColor = banner.backgroundColor;
+ }
+
+ if (banner.textColor)
+ {
+ bgColor = banner.textColor;
+ }
+ }
+
+ const rest = banner?.additionalStyles ?? {};
+
+ return ({
+ backgroundColor: bgColor,
+ color: color,
+ ...rest
+ });
+}
+
+
+/***************************************************************************
+ **
+ ***************************************************************************/
+export function getBannerClassName(banner: Banner)
+{
+ return `banner ${banner?.severity?.toLowerCase()}`;
+}
+
+
+/***************************************************************************
+ **
+ ***************************************************************************/
+export function makeBannerContent(banner: Banner): JSX.Element
+{
+ return <>{banner?.messageHTML ? parse(banner?.messageHTML) : banner?.messageText}>;
+}
+
diff --git a/src/qqq/layouts/BaseLayout.tsx b/src/qqq/layouts/BaseLayout.tsx
index 94f6aea..d56791d 100644
--- a/src/qqq/layouts/BaseLayout.tsx
+++ b/src/qqq/layouts/BaseLayout.tsx
@@ -21,11 +21,12 @@
import {QInstance} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QInstance";
import Box from "@mui/material/Box";
-import {ReactNode, useEffect, useState} from "react";
import Footer from "qqq/components/horseshoe/Footer";
import NavBar from "qqq/components/horseshoe/NavBar";
+import {getBannerClassName, getBannerStyles, getBanner, makeBannerContent} from "qqq/components/misc/Banners";
import DashboardLayout from "qqq/layouts/DashboardLayout";
import Client from "qqq/utils/qqq/Client";
+import {ReactNode, useEffect, useState} from "react";
interface Props
{
@@ -80,12 +81,34 @@ function BaseLayout({stickyNavbar, children}: Props): JSX.Element
return () => window.removeEventListener("resize", handleTabsOrientation);
}, [tabsOrientation]);
+
+ /***************************************************************************
+ **
+ ***************************************************************************/
+ function banner(): JSX.Element | null
+ {
+ const banner = getBanner(metaData?.branding, "QFMD_TOP_OF_BODY");
+
+ if (!banner)
+ {
+ return (null);
+ }
+
+ return (
+ {makeBannerContent(banner)}
+ );
+ }
+
+
return (
-
-
- {children}
-
-
+ <>
+
+ {banner()}
+
+ {children}
+
+
+ >
);
}
diff --git a/src/qqq/styles/qqq-override-styles.css b/src/qqq/styles/qqq-override-styles.css
index f95797b..dc3c861 100644
--- a/src/qqq/styles/qqq-override-styles.css
+++ b/src/qqq/styles/qqq-override-styles.css
@@ -748,35 +748,54 @@ input[type="search"]::-webkit-search-results-decoration
padding: 8px 0;
}
-.helpContentAlert.success
+.helpContentAlert.info,
+.banner.info
+{
+ background-color: rgb(234, 242, 255);
+ color: rgb(20, 51, 102);
+}
+
+.helpContentAlert.info .MuiAlert-icon .material-icons-round,
+.banner.info .MuiAlert-icon .material-icons-round
+{
+ color: #0062FF;
+}
+
+.helpContentAlert.success,
+.banner.success
{
background-color: rgb(240, 248, 241);
color: rgb(44, 76, 46);
}
-.helpContentAlert.success .MuiAlert-icon .material-icons-round
+.helpContentAlert.success .MuiAlert-icon .material-icons-round,
+.banner.success .MuiAlert-icon .material-icons-round
{
color: #4CAF50;
}
-.helpContentAlert.warning
+.helpContentAlert.warning,
+.banner.warning
{
background-color: rgb(254, 245, 234);
color: rgb(100, 65, 20);
}
-.helpContentAlert.warning .MuiAlert-icon .material-icons-round
+.helpContentAlert.warning .MuiAlert-icon .material-icons-round,
+.banner.warning .MuiAlert-icon .material-icons-round
{
color: #fb8c00;
}
-.helpContentAlert.error
+.helpContentAlert.error,
+.banner.error
{
background-color: rgb(254, 239, 238);
color: rgb(98, 41, 37);
}
-.helpContentAlert.error .MuiAlert-icon .material-icons-round
+.helpContentAlert.error .MuiAlert-icon .material-icons-round,
+.banner.error .MuiAlert-icon .material-icons-round
{
color: #F44335;
}