diff --git a/src/front/Components/DesignSystem/Header/BurgerMenu/index.tsx b/src/front/Components/DesignSystem/Header/BurgerMenu/index.tsx index a94a30c1..12d11607 100644 --- a/src/front/Components/DesignSystem/Header/BurgerMenu/index.tsx +++ b/src/front/Components/DesignSystem/Header/BurgerMenu/index.tsx @@ -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 { 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 {
burger - {this.state.isModalOpen && } + {this.props.isModalOpen && }
); } - - private openModal() { - this.setState({ isModalOpen: true }); - } - - private closeModal() { - this.setState({ isModalOpen: false }); - } } diff --git a/src/front/Components/DesignSystem/Header/Notifications/index.tsx b/src/front/Components/DesignSystem/Header/Notifications/index.tsx index 6ca96b84..f12c7ed5 100644 --- a/src/front/Components/DesignSystem/Header/Notifications/index.tsx +++ b/src/front/Components/DesignSystem/Header/Notifications/index.tsx @@ -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 { - 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 (
-
+
notifications {this.state.hasNotifications && ( Unread notification )}
- {this.state.isModalOpen && } + {this.props.isModalOpen && }
); } @@ -57,12 +57,4 @@ export default class Notifications extends React.Component { hasNotifications: toastList ? toastList.length > 0 : false, }); } - - private openModal() { - this.setState({ isModalOpen: true }); - } - - private closeModal() { - this.setState({ isModalOpen: false }); - } } diff --git a/src/front/Components/DesignSystem/Header/Profile/index.tsx b/src/front/Components/DesignSystem/Header/Profile/index.tsx index 6a3cc824..71901561 100644 --- a/src/front/Components/DesignSystem/Header/Profile/index.tsx +++ b/src/front/Components/DesignSystem/Header/Profile/index.tsx @@ -4,9 +4,12 @@ import Image from "next/image"; import ProfileIcon from "@Assets/icons/user.svg"; import ProfileModal from "./ProfileModal"; -type IProps = {}; -type IState = { +type IProps = { isModalOpen: boolean; + openProfileModal: () => void; + closeProfileModal: () => void; +}; +type IState = { }; export default class Profile extends React.Component { @@ -15,24 +18,14 @@ export default class Profile extends React.Component { this.state = { isModalOpen: false, }; - this.openModal = this.openModal.bind(this); - this.closeModal = this.closeModal.bind(this); } public override render(): JSX.Element { return (
- profile - {this.state.isModalOpen && } + profile + {this.props.isModalOpen && }
); } - - private openModal() { - this.setState({ isModalOpen: true }); - } - - private closeModal() { - this.setState({ isModalOpen: false }); - } } diff --git a/src/front/Components/DesignSystem/Header/classes.module.scss b/src/front/Components/DesignSystem/Header/classes.module.scss index 42129e2f..5f5b00af 100644 --- a/src/front/Components/DesignSystem/Header/classes.module.scss +++ b/src/front/Components/DesignSystem/Header/classes.module.scss @@ -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; } } } -} +} \ No newline at end of file diff --git a/src/front/Components/DesignSystem/Header/index.tsx b/src/front/Components/DesignSystem/Header/index.tsx index 18fd9777..b8c176be 100644 --- a/src/front/Components/DesignSystem/Header/index.tsx +++ b/src/front/Components/DesignSystem/Header/index.tsx @@ -7,14 +7,49 @@ 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; + isProfileMenuOpen: boolean; +}; export default class Header extends React.Component { + private onScrollYDirectionChange = () => { }; + private onWindowResize = () => { }; + private headerBreakpoint = 1300; + + constructor(props: IProps) { + super(props); + + this.state = { + open: EHeaderOpeningState.OPEN, + isBurgerMenuOpen: false, + isNotificationMenuOpen: false, + isProfileMenuOpen: 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.closeProfileMenu = this.closeProfileMenu.bind(this); + this.openProfileMenu = this.openProfileMenu.bind(this); + this.visibility = this.visibility.bind(this); + } public override render(): JSX.Element { return ( -
+
logo @@ -23,16 +58,71 @@ export default class Header extends React.Component {
- +
- +
- +
); } + + public override componentDidMount() { + this.onScrollYDirectionChange = WindowStore.getInstance().onScrollYDirectionChange((scrollYDirection) => + this.visibility(scrollYDirection), + ); + + this.onWindowResize = WindowStore.getInstance().onResize((window) => + this.onResize(window) + ); + } + + public override componentWillUnmount() { + this.onScrollYDirectionChange(); + this.onWindowResize(); + } + + private visibility(scrollYDirection: number) { + let open: IState["open"] = EHeaderOpeningState.OPEN; + if (window.scrollY > 50 && scrollYDirection < 0 && Math.abs(scrollYDirection) > 8 && window.innerWidth < this.headerBreakpoint) { + open = EHeaderOpeningState.CLOSED; + this.closeBurgerMenu(); + this.closeNotificationMenu(); + } + + if (open !== this.state.open) this.setState({ open }); + } + + private onResize(window: Window) { + if(window.innerWidth > this.headerBreakpoint && this.state.isBurgerMenuOpen) this.setState({ isBurgerMenuOpen: false }); + if (window.innerWidth < this.headerBreakpoint && this.state.isProfileMenuOpen) this.setState({ isProfileMenuOpen: false }); + } + + private openBurgerMenu() { + this.setState({ isBurgerMenuOpen: true }); + } + + private closeBurgerMenu() { + this.setState({ isBurgerMenuOpen: false }); + } + + private openNotificationMenu() { + this.setState({ isNotificationMenuOpen: true }); + } + + private closeNotificationMenu() { + this.setState({ isNotificationMenuOpen: false }); + } + + private openProfileMenu() { + this.setState({ isProfileMenuOpen: true }); + } + + private closeProfileMenu() { + this.setState({ isProfileMenuOpen: false }); + } } diff --git a/src/front/Stores/WindowStore.ts b/src/front/Stores/WindowStore.ts new file mode 100644 index 00000000..f40fb3f6 --- /dev/null +++ b/src/front/Stores/WindowStore.ts @@ -0,0 +1,56 @@ +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); + }; + } + + public onResize(callback: (window: Window) => void) { + this.event.on("resize", callback); + return () => { + this.event.off("resize", callback); + }; + } + + private iniEvents(): void { + window.addEventListener("scroll", (e: Event) => this.scrollYHandler(e)); + window.addEventListener("resize", (e: Event) => this.resizeHandler()); + + } + + 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; + }; + })(); + + private resizeHandler() { + this.event.emit("resize", window); + } +}