diff --git a/src/app/Layout.tsx b/src/app/Layout.tsx index 76c358e287..f0650ec4c6 100644 --- a/src/app/Layout.tsx +++ b/src/app/Layout.tsx @@ -5,13 +5,13 @@ import PageContainer from 'components/shared/PageContainer'; import DocsSidePanel from 'components/sidePanelDocs/SidePanel'; import { useShowSidePanelDocs } from 'context/SidePanelDocs'; import { NavWidths } from 'context/Theme'; +import { useEffect, useState } from 'react'; import { ReflexContainer, ReflexElement, ReflexSplitter } from 'react-reflex'; import { Outlet } from 'react-router'; import { useLocalStorage } from 'react-use'; +import { useSidePanelDocsStore } from 'stores/SidePanelDocs/Store'; import { LocalStorageKeys } from 'utils/localStorage-utils'; -import { useEffect, useState } from 'react'; import { hasLength } from 'utils/misc-utils'; -import { useSidePanelDocsStore } from 'stores/SidePanelDocs/Store'; function AppLayout() { const theme = useTheme(); diff --git a/src/components/navigation/Banner.tsx b/src/components/navigation/Banner.tsx new file mode 100644 index 0000000000..d80a47f854 --- /dev/null +++ b/src/components/navigation/Banner.tsx @@ -0,0 +1,97 @@ +import { + Collapse, + IconButton, + Stack, + Typography, + useTheme, +} from '@mui/material'; +import { NavArrowDown } from 'iconoir-react'; +import { useEffect, useRef, useState } from 'react'; +import { useTopBarStore } from 'stores/TopBar/Store'; +import { + BANNER_HEIGHT, + getSemanticBackgroundColor, + getSemanticBorder, + isOverflown, +} from './shared'; + +export default function Banner() { + const theme = useTheme(); + + const contentEl = useRef(null); + + const bannerOpen = useTopBarStore((state) => state.bannerOpen); + const setBannerOpen = useTopBarStore((state) => state.setBannerOpen); + + const [severity, setSeverity] = useState(''); + const [expanded, setExpanded] = useState(false); + const [overflown, setOverflown] = useState(false); + + useEffect(() => { + setSeverity('error'); + setBannerOpen(true); + setOverflown(isOverflown(contentEl.current)); + }, [setBannerOpen, setOverflown, setSeverity]); + + return ( + + + + Here is a short message that could potentially be close to + the length of a real message. Here is a short message that + could potentially be close to the length of a real message. + Here is a short message that could potentially be close to + the length of a real message. Here is a short message that + could potentially be close to the length of a real message. + Here is a short message that could potentially be close to + the length of a real message. Here is a short message that + could potentially be close to the length of a real message. + Here is a short message that could potentially be close to + the length of a real message. Here is a short message that + could potentially be close to the length of a real message. + + + {overflown ? ( + { + setExpanded(!expanded); + }} + style={{ padding: 0 }} + > + + + ) : null} + + + ); +} diff --git a/src/components/navigation/Navigation.tsx b/src/components/navigation/Navigation.tsx index 76cd6bce43..b427681066 100644 --- a/src/components/navigation/Navigation.tsx +++ b/src/components/navigation/Navigation.tsx @@ -23,18 +23,17 @@ import { Settings, } from 'iconoir-react'; import { useIntl } from 'react-intl'; +import { useTopBarStore } from 'stores/TopBar/Store'; import ListItemLink from './ListItemLink'; - -interface NavigationProps { - open: boolean; - width: number; - onNavigationToggle: Function; -} +import { BANNER_HEIGHT } from './shared'; +import { NavigationProps } from './types'; const Navigation = ({ open, width, onNavigationToggle }: NavigationProps) => { const intl = useIntl(); const theme = useTheme(); + const bannerOpen = useTopBarStore((state) => state.bannerOpen); + const openNavigation = () => { onNavigationToggle(true); }; @@ -70,6 +69,9 @@ const Navigation = ({ open, width, onNavigationToggle }: NavigationProps) => { sx={{ height: '100%', justifyContent: 'space-between', + marginTop: bannerOpen + ? `${BANNER_HEIGHT - 8}px` + : undefined, overflowX: 'hidden', }} > diff --git a/src/components/navigation/TopBar.tsx b/src/components/navigation/TopBar.tsx index 1e99f68440..f71ce0f445 100644 --- a/src/components/navigation/TopBar.tsx +++ b/src/components/navigation/TopBar.tsx @@ -8,8 +8,9 @@ import PageTitle from 'components/navigation/PageTitle'; import SidePanelDocsOpenButton from 'components/sidePanelDocs/OpenButton'; import { UpdateAlert } from 'components/UpdateAlert'; import { zIndexIncrement } from 'context/Theme'; +import Banner from './Banner'; -const Topbar = () => { +const TopBar = () => { const theme = useTheme(); return ( @@ -48,8 +49,10 @@ const Topbar = () => { + + ); }; -export default Topbar; +export default TopBar; diff --git a/src/components/navigation/shared.ts b/src/components/navigation/shared.ts new file mode 100644 index 0000000000..4e4972fc77 --- /dev/null +++ b/src/components/navigation/shared.ts @@ -0,0 +1,53 @@ +import { PaletteMode } from '@mui/material'; +import { + errorColoredOutline_hovered, + infoColoredOutline_hovered, + semiTransparentBackground_error, + semiTransparentBackground_info, + semiTransparentBackground_success, + semiTransparentBackground_warning, + successColoredOutline_hovered, + warningColoredOutline_hovered, +} from 'context/Theme'; + +export const BANNER_HEIGHT = 28; + +export const getSemanticBackgroundColor = ( + colorMode: PaletteMode, + severity: string +): string => { + switch (severity) { + case 'success': + return semiTransparentBackground_success[colorMode]; + case 'error': + return semiTransparentBackground_error[colorMode]; + case 'info': + return semiTransparentBackground_info[colorMode]; + default: + return semiTransparentBackground_warning[colorMode]; + } +}; + +export const getSemanticBorder = ( + colorMode: PaletteMode, + severity: string +): string => { + switch (severity) { + case 'success': + return successColoredOutline_hovered[colorMode]; + case 'error': + return errorColoredOutline_hovered[colorMode]; + case 'info': + return infoColoredOutline_hovered[colorMode]; + default: + return warningColoredOutline_hovered[colorMode]; + } +}; + +export const isOverflown = (el: HTMLElement | null) => { + if (!el) { + return false; + } + + return el.scrollHeight > el.clientHeight || el.scrollWidth > el.clientWidth; +}; diff --git a/src/components/navigation/types.ts b/src/components/navigation/types.ts new file mode 100644 index 0000000000..61692460d5 --- /dev/null +++ b/src/components/navigation/types.ts @@ -0,0 +1,5 @@ +export interface NavigationProps { + open: boolean; + width: number; + onNavigationToggle: Function; +} diff --git a/src/components/shared/PageContainer.tsx b/src/components/shared/PageContainer.tsx index 5b536b3a68..32eeaf82ac 100644 --- a/src/components/shared/PageContainer.tsx +++ b/src/components/shared/PageContainer.tsx @@ -1,5 +1,5 @@ import { Container, Paper, Snackbar, useTheme } from '@mui/material'; -import Topbar from 'components/navigation/TopBar'; +import TopBar from 'components/navigation/TopBar'; import { paperBackground } from 'context/Theme'; import { ReactNode, useEffect, useMemo, useState } from 'react'; import useNotificationStore, { @@ -108,7 +108,7 @@ function PageContainer({ children, hideBackground }: Props) { ) : null} - + => ({ +const getInitialStateData = (): Pick< + TopBarState, + 'bannerOpen' | 'header' | 'headerLink' +> => ({ + bannerOpen: false, header: '', headerLink: undefined, }); @@ -16,6 +20,16 @@ const getInitialState = ( ): TopBarState => ({ ...getInitialStateData(), + setBannerOpen: (value) => { + set( + produce((state: TopBarState) => { + state.bannerOpen = value; + }), + false, + 'Application Banner Open Set' + ); + }, + setHeader: (val) => { set( produce((state: TopBarState) => { diff --git a/src/stores/TopBar/types.ts b/src/stores/TopBar/types.ts index 2d5e6ee732..8689ed04bc 100644 --- a/src/stores/TopBar/types.ts +++ b/src/stores/TopBar/types.ts @@ -7,5 +7,8 @@ export interface TopBarState { headerLink: string | undefined; setHeaderLink: (val: TopBarState['headerLink']) => void; + bannerOpen: boolean; + setBannerOpen: (value: TopBarState['bannerOpen']) => void; + resetState: () => void; }