Skip to content

Commit

Permalink
[B2BP-559] - Implementing 'Header' EC inside B2BP (#268)
Browse files Browse the repository at this point in the history
* Commit migrate Header

* Commit changeset

* Commit fix Header files

* commit code fix

* commit review changes

* commit file fix

* commit review changes

* commit review changes
  • Loading branch information
Meolocious authored May 9, 2024
1 parent 80e1e13 commit 255f730
Show file tree
Hide file tree
Showing 11 changed files with 466 additions and 42 deletions.
5 changes: 5 additions & 0 deletions .changeset/happy-beans-fry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"nextjs-website": minor
---

Migrate Header component from EC to B2BP
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { Stack, useTheme, type Theme } from '@mui/material';
import { DialogBubbleProps } from '../../types/Header/Header.types';

const useStyles = (muiTheme: Theme) => ({
bubbleContainer: {
transform: 'rotate(180deg)',
position: 'absolute',
marginTop: '42px',
padding: muiTheme.spacing(2),
direction: 'rtl',
textAlign: 'left',
borderRadius: '4px',
},
bubble: { transform: 'rotate(180deg)' },
});

export const DialogBubble = ({
children,
...stackProps
}: DialogBubbleProps) => {
const muiTheme = useTheme();
const styles = useStyles(muiTheme);
return (
<Stack
sx={{ ...styles.bubbleContainer, bgcolor: muiTheme.palette.common.white }}
aria-haspopup="true"
>
<Stack sx={styles.bubble} {...stackProps}>
{children}
</Stack>
</Stack>
);
};
120 changes: 120 additions & 0 deletions apps/nextjs-website/react-components/components/Header/Header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import Box from '@mui/material/Box';
import Stack from '@mui/material/Stack';
import { useEffect, useState } from 'react';
import { HamburgerMenu } from './components/HamburgerMenu';
import { Navigation } from './components/Navigation';
import { HeaderTitle } from './components/Title';
import { HeaderProps } from '@react-components/types/Header/Header.types';
import { CtaButtons } from '../common/Common';
import { BackgroundColor } from '../common/Common.helpers';

const Header = ({
product,
theme,
menu,
ctaButtons,
avatar,
beta,
logo,
}: HeaderProps) => {
const backgroundColor = BackgroundColor(theme);

const [menuOpen, setMenuOpen] = useState(false);

const openHeader = () => {
setMenuOpen(true);
};

const closeHeader = () => {
setMenuOpen(false);
};

useEffect(() => {
window.addEventListener('resize', closeHeader);

return () => {
window.removeEventListener('resize', closeHeader);
};
}, []);

const HeaderCtas = () => {
return ctaButtons && ctaButtons.length > 0 ? (
<Stack direction='row'>
{CtaButtons({
ctaButtons: ctaButtons.map((CtaButton) => ({
...CtaButton,
sx: { width: { md: 'auto', xs: '100%' } },
})),
theme,
})}
</Stack>
) : null;
};

return (
<Box
bgcolor={backgroundColor}
paddingX={{ xs: 1, sm: 3 }}
component='header'
role='banner'
>
<Stack
direction={{ md: 'row' }}
paddingY={{ xs: 2, sm: 3, md: 0 }}
gap={4}
>
<Stack
sx={{
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
}}
>
<HeaderTitle
theme={theme}
product={product}
{...(beta ? { beta } : {})}
{...(avatar ? { avatar } : {})}
{...(logo ? { logo } : {})}
/>

<Stack
sx={{ display: { md: 'none' } }}
direction='row'
alignItems='center'
gap={4}
>
<Box sx={{ display: { xs: 'none', sm: 'block', md: 'none' } }}>
<HeaderCtas />
</Box>

<HamburgerMenu
onOpen={openHeader}
onClose={closeHeader}
open={menuOpen}
/>
</Stack>
</Stack>

<Stack
sx={{
justifyContent: 'space-between',
width: '100%',
flexDirection: { xs: 'column', md: 'row' },
alignItems: { md: 'center', xs: 'flex-start' },
gap: { xs: 2 },
display: { xs: menuOpen ? 'flex' : 'none', md: 'flex' },
}}
>
<Navigation {...{ menu, theme }} />

<Box sx={{ display: { sm: 'none', md: 'block' } }}>
<HeaderCtas />
</Box>
</Stack>
</Stack>
</Box>
);
};

export default Header;
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import CloseIcon from '@mui/icons-material/Close';
import MenuIcon from '@mui/icons-material/Menu';

interface HamburgerMenuProps {
open: boolean;
onOpen: () => void;
onClose: () => void;
}

export const HamburgerMenu = ({ open, onOpen, onClose }: HamburgerMenuProps) =>
open ? (
<CloseIcon
color="primary"
cursor="pointer"
onClick={onClose}
aria-label="chiudi"
aria-haspopup="true"
aria-expanded="true"
/>
) : (
<MenuIcon
color="primary"
cursor="pointer"
onClick={onOpen}
aria-label="apri"
aria-haspopup="true"
aria-expanded="false"
/>
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import {
Stack,
type StackProps,
Typography,
useTheme,
useMediaQuery,
Link,
type Theme,
} from '@mui/material';
import { useState } from 'react';
import { ArrowDropDown } from '@mui/icons-material';
import { DialogBubble } from '../Header.helpers';
import { isJSX } from '../../../types/common/Common.types';
import { DropdownItem, MenuDropdownProp } from '../../../types/Header/Header.types';
import { TextAlternativeColor } from '@react-components/components/common/Common.helpers';

const TIMEOUT_LENGTH = 100;

const useStyles = ({ theme, active, items }: MenuDropdownProp, { palette, spacing }: Theme) => {
const textColor = TextAlternativeColor(theme);

return {
menu: {
paddingY: { md: 2 },
borderColor: textColor,
borderBottomStyle: 'solid',
borderBottomWidth: { md: active ? 3 : 0, xs: 0 },
},
item: {
cursor: {
md: items?.length ? 'default' : 'pointer',
xs: 'pointer',
},
flexDirection: 'row',
color: textColor,
textDecoration: 'none',
},
link: {
color: { xs: textColor, md: palette.primary.contrastText },
textIndent: { xs: spacing(2), md: 0 },
},
arrowAnimate: {
transition: 'transform 0.2s',
transform: { xs: 'rotate(-180deg)', md: '' },
},
};
};

export const MenuDropdown = (props: MenuDropdownProp) => {
// props
const { label, active, theme, items, ...button } = props;

// state
const [menuHover, setMenuHover] = useState(false);
const [dropdownHover, setDropdownHover] = useState(false);

const hoverOnMenu = () => {
setMenuHover(true);
};

const leavesMenu = () => {
setTimeout(() => {
setMenuHover(false);
}, TIMEOUT_LENGTH);
};

const hoverOnDropdown = () => {
setDropdownHover(true);
};

const leavesDropdown = () => {
setTimeout(() => {
setDropdownHover(false);
}, TIMEOUT_LENGTH);
};

const toggleMenu = () => {
setMenuHover((status) => !status);
};

// style
const hasLinks = items?.length;
const muiTheme = useTheme();
const md = useMediaQuery(muiTheme.breakpoints.up('md'));
const styles = useStyles(props, muiTheme);

const dropdownVisible = menuHover || dropdownHover;

const menuEventsHandlers = md
? {
onMouseEnter: hoverOnMenu,
onMouseLeave: leavesMenu,
}
: {
onClick: toggleMenu,
};

const Dropdown = ({
children,
...stackProps
}: { children: JSX.Element[] } & StackProps) =>
md ? (
<DialogBubble
{...stackProps}
onMouseEnter={hoverOnDropdown}
onMouseLeave={leavesDropdown}
>
{children}
</DialogBubble>
) : (
<Stack {...stackProps} onClick={toggleMenu}>
{children}
</Stack>
);

return (
<Stack sx={styles.menu} {...menuEventsHandlers}>
<Link sx={styles.item} {...button}>
<Typography variant="sidenav" color="inherit">
{label}
</Typography>
{hasLinks && (
<ArrowDropDown
color="inherit"
fontSize="small"
sx={{
...(!md && dropdownVisible && styles.arrowAnimate),
}}
/>
)}
</Link>
{hasLinks && dropdownVisible && (
<Dropdown gap={1}>
{items?.map((item: DropdownItem, index) =>
isJSX(item) ? (
item
) : (
<Link
variant="body1"
underline="none"
key={item.key ?? index}
sx={styles.link}
{...item}
>
{item.label}
</Link>
)
)}
</Dropdown>
)}
</Stack>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Stack } from '@mui/material';
import { MenuDropdown } from './MenuDropdown';
import { NavigationProps } from '../../../types/Header/Header.types';

export const Navigation = ({ menu, theme }: NavigationProps) => (
<Stack
gap={{ md: 4, xs: 2 }}
direction={{ md: 'row', xs: 'column' }}
component="nav"
aria-label="main"
>
{menu.map((menu, index) => (
<MenuDropdown key={index} {...menu} theme={theme} />
))}
</Stack>
);
Loading

0 comments on commit 255f730

Please sign in to comment.