add some animation on header closing submenu if menu disapear

This commit is contained in:
Hugo Lextrait 2023-03-21 14:30:39 +01:00
parent 908963aa5a
commit c88a180acc
5 changed files with 146 additions and 41 deletions

View File

@ -5,19 +5,18 @@ import BurgerIcon from "@Assets/icons/burger.svg";
import CrossIcon from "@Assets/icons/cross.svg";
import BurgerModal from "./BurgerModal";
type IProps = {};
type IState = {
type IProps = {
isModalOpen: boolean;
openBurgerMenu: () => void;
closeBurgerMenu: () => void;
};
type IState = {
// isModalOpen: boolean;
};
export default class BurgerMenu extends React.Component<IProps, IState> {
constructor(props: IProps) {
super(props);
this.state = {
isModalOpen: false,
};
this.openModal = this.openModal.bind(this);
this.closeModal = this.closeModal.bind(this);
}
public override render(): JSX.Element {
@ -25,20 +24,12 @@ export default class BurgerMenu extends React.Component<IProps, IState> {
<div className={classes["root"]}>
<Image
alt="burger"
src={this.state.isModalOpen ? CrossIcon : BurgerIcon}
src={this.props.isModalOpen ? CrossIcon : BurgerIcon}
className={classes["burger-icon"]}
onClick={this.openModal}
onClick={this.props.openBurgerMenu}
/>
{this.state.isModalOpen && <BurgerModal isOpen={this.state.isModalOpen} closeModal={this.closeModal} />}
{this.props.isModalOpen && <BurgerModal isOpen={this.props.isModalOpen} closeModal={this.props.closeBurgerMenu} />}
</div>
);
}
private openModal() {
this.setState({ isModalOpen: true });
}
private closeModal() {
this.setState({ isModalOpen: false });
}
}

View File

@ -6,39 +6,39 @@ import Toasts, { IToast } from "@Front/Stores/Toasts";
import NotificationModal from "./NotificationModal";
import InfoIcon from "@Assets/icons/info.svg";
type IProps = {};
type IProps = {
isModalOpen: boolean;
openNotificationModal: () => void;
closeNotificationModal: () => void;
};
type IState = {
hasNotifications: boolean;
isModalOpen: boolean;
toastList: IToast[] | null;
};
export default class Notifications extends React.Component<IProps, IState> {
private removeOnToastChange: () => void = () => {};
private removeOnToastChange: () => void = () => { };
constructor(props: IProps) {
super(props);
this.state = {
isModalOpen: false,
toastList: Toasts.getInstance().toasts, //TODO : Get from bbd
hasNotifications: Toasts.getInstance().toasts.length > 0, // TODO: Change this when we have notification stored in bbd, unread notifications
};
this.openModal = this.openModal.bind(this);
this.closeModal = this.closeModal.bind(this);
this.handleToastChange = this.handleToastChange.bind(this);
}
public override render(): JSX.Element {
return (
<div className={classes["root"]}>
<div className={classes["icon-container"]} onClick={this.openModal}>
<div className={classes["icon-container"]} onClick={this.props.openNotificationModal}>
<Image alt="notifications" src={NotificationIcon} className={classes["notification-icon"]} />
{this.state.hasNotifications && (
<Image className={classes["notification-dot"]} src={InfoIcon} alt="Unread notification" />
)}
</div>
{this.state.isModalOpen && <NotificationModal isOpen={this.state.isModalOpen} closeModal={this.closeModal} />}
{this.props.isModalOpen && <NotificationModal isOpen={this.props.isModalOpen} closeModal={this.props.closeNotificationModal} />}
</div>
);
}
@ -57,12 +57,4 @@ export default class Notifications extends React.Component<IProps, IState> {
hasNotifications: toastList ? toastList.length > 0 : false,
});
}
private openModal() {
this.setState({ isModalOpen: true });
}
private closeModal() {
this.setState({ isModalOpen: false });
}
}

View File

@ -8,7 +8,16 @@
background-color: $white;
box-shadow: $shadow-nav;
padding: 0 48px;
position: relative;
position: sticky;
top: 0;
z-index: 1;
@media (max-width: $screen-m) {
transition: transform 500ms ease-out;
&[data-open="closed"] {
transform: translateY(-85px);
}
}
.logo-container {
.logo {
@ -20,6 +29,7 @@
.right-section {
.profile-section {
display: inline-flex;
> :first-child {
margin-right: 32px;
}
@ -31,6 +41,7 @@
.notification-section {
display: inline-flex;
> :first-child {
margin-right: 32px;
}
@ -39,12 +50,14 @@
.burger-menu {
display: none;
@media (max-width: $screen-ls) {
display: inline-flex;
.icon {
width: 24px;
height: 24px;
}
}
}
}
}

View File

@ -7,14 +7,43 @@ import Navigation from "./Navigation";
import Notifications from "./Notifications";
import Profile from "./Profile";
import BurgerMenu from "./BurgerMenu";
import WindowStore from "@Front/Stores/WindowStore";
type IProps = {};
type IState = {};
enum EHeaderOpeningState {
OPEN = "open",
CLOSED = "closed",
IDLE = "idle",
}
type IProps = {
};
type IState = {
open: EHeaderOpeningState;
isBurgerMenuOpen: boolean;
isNotificationMenuOpen: boolean;
};
export default class Header extends React.Component<IProps, IState> {
private onScrollYDirectionChange = () => { };
constructor(props: IProps) {
super(props);
this.state = {
open: EHeaderOpeningState.OPEN,
isBurgerMenuOpen: false,
isNotificationMenuOpen: false,
};
this.openBurgerMenu = this.openBurgerMenu.bind(this);
this.closeBurgerMenu = this.closeBurgerMenu.bind(this);
this.openNotificationMenu = this.openNotificationMenu.bind(this);
this.closeNotificationMenu = this.closeNotificationMenu.bind(this);
this.visibility = this.visibility.bind(this);
}
public override render(): JSX.Element {
return (
<div className={classes["root"]}>
<div className={classes["root"]} data-open={this.state.open}>
<div className={classes["logo-container"]}>
<Link href="/">
<Image src={LogoIcon} alt="logo" className={classes["logo"]} />
@ -23,16 +52,53 @@ export default class Header extends React.Component<IProps, IState> {
<Navigation />
<div className={classes["right-section"]}>
<div className={classes["notification-section"]}>
<Notifications />
<Notifications isModalOpen={this.state.isNotificationMenuOpen} openNotificationModal={this.openNotificationMenu} closeNotificationModal={this.closeNotificationMenu} />
</div>
<div className={classes["profile-section"]}>
<Profile />
</div>
<div className={classes["burger-menu"]}>
<BurgerMenu />
<BurgerMenu isModalOpen={this.state.isBurgerMenuOpen} closeBurgerMenu={this.closeBurgerMenu} openBurgerMenu={this.openBurgerMenu} />
</div>
</div>
</div>
);
}
public override componentDidMount() {
this.onScrollYDirectionChange = WindowStore.getInstance().onScrollYDirectionChange((scrollYDirection) =>
this.visibility(scrollYDirection),
);
}
public override componentWillUnmount() {
this.onScrollYDirectionChange();
}
private visibility(scrollYDirection: number) {
let open: IState["open"] = EHeaderOpeningState.OPEN;
if (window.scrollY > 50 && scrollYDirection < 0 && Math.abs(scrollYDirection) > 8) {
open = EHeaderOpeningState.CLOSED;
this.closeBurgerMenu();
this.closeNotificationMenu();
}
if (open !== this.state.open) this.setState({ open });
}
private openBurgerMenu() {
this.setState({ isBurgerMenuOpen: true });
}
private closeBurgerMenu() {
this.setState({ isBurgerMenuOpen: false });
}
private openNotificationMenu() {
this.setState({ isNotificationMenuOpen: true });
}
private closeNotificationMenu() {
this.setState({ isNotificationMenuOpen: false });
}
}

View File

@ -0,0 +1,43 @@
import EventEmitter from "events";
export default class WindowStore {
private static ctx: WindowStore;
private readonly event = new EventEmitter();
private constructor() {
WindowStore.ctx = this;
this.iniEvents();
}
public static getInstance() {
if (!WindowStore.ctx) new this();
return WindowStore.ctx;
}
public onScrollYDirectionChange(callback: (scrollYDifference: number) => void) {
this.event.on("scrollYDirectionChange", callback);
return () => {
this.event.off("scrollYDirectionChange", callback);
};
}
private iniEvents(): void {
window.addEventListener("scroll", (e: Event) => this.scrollYHandler(e));
}
private scrollYHandler = (() => {
let previousY: number = window.scrollY;
let snapShotY: number = previousY;
let previousYDirection: number = 1;
return (e: Event): void => {
const scrollYDirection = window.scrollY - previousY > 0 ? 1 : -1;
if (previousYDirection !== scrollYDirection) {
snapShotY = window.scrollY;
}
this.event.emit("scrollYDirectionChange", snapShotY - window.scrollY);
previousY = window.scrollY;
previousYDirection = scrollYDirection;
};
})();
}