From c88a180accf78b142e559d9aa40927b83c815c8f Mon Sep 17 00:00:00 2001 From: Hugo Lextrait Date: Tue, 21 Mar 2023 14:30:39 +0100 Subject: [PATCH 1/3] :sparkles: add some animation on header closing submenu if menu disapear --- .../DesignSystem/Header/BurgerMenu/index.tsx | 27 +++---- .../Header/Notifications/index.tsx | 24 ++---- .../DesignSystem/Header/classes.module.scss | 17 ++++- .../Components/DesignSystem/Header/index.tsx | 76 +++++++++++++++++-- src/front/Stores/WindowStore.ts | 43 +++++++++++ 5 files changed, 146 insertions(+), 41 deletions(-) create mode 100644 src/front/Stores/WindowStore.ts 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/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..96900ebd 100644 --- a/src/front/Components/DesignSystem/Header/index.tsx +++ b/src/front/Components/DesignSystem/Header/index.tsx @@ -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 { + 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 ( -
+
logo @@ -23,16 +52,53 @@ export default class Header extends React.Component {
- +
- +
); } + + 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 }); + } } diff --git a/src/front/Stores/WindowStore.ts b/src/front/Stores/WindowStore.ts new file mode 100644 index 00000000..8ae1e77b --- /dev/null +++ b/src/front/Stores/WindowStore.ts @@ -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; + }; + })(); +} From fb5be8caa9c09fd5074da7840cc6cf19dc5facf2 Mon Sep 17 00:00:00 2001 From: Hugo Lextrait Date: Tue, 21 Mar 2023 14:57:10 +0100 Subject: [PATCH 2/3] :art: add functionalities when submenu close on resize breakpoint using windowStore --- .../DesignSystem/Header/Profile/index.tsx | 21 +++++---------- .../Components/DesignSystem/Header/index.tsx | 27 ++++++++++++++++++- src/front/Stores/WindowStore.ts | 13 +++++++++ 3 files changed, 46 insertions(+), 15 deletions(-) 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/index.tsx b/src/front/Components/DesignSystem/Header/index.tsx index 96900ebd..403882ad 100644 --- a/src/front/Components/DesignSystem/Header/index.tsx +++ b/src/front/Components/DesignSystem/Header/index.tsx @@ -22,10 +22,12 @@ type IState = { open: EHeaderOpeningState; isBurgerMenuOpen: boolean; isNotificationMenuOpen: boolean; + isProfileMenuOpen: boolean; }; export default class Header extends React.Component { private onScrollYDirectionChange = () => { }; + private onWindowResize = () => { }; constructor(props: IProps) { super(props); @@ -34,11 +36,14 @@ export default class Header extends React.Component { 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 { @@ -55,7 +60,7 @@ export default class Header extends React.Component {
- +
@@ -69,10 +74,15 @@ export default class Header extends React.Component { 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) { @@ -86,6 +96,13 @@ export default class Header extends React.Component { if (open !== this.state.open) this.setState({ open }); } + private onResize(window: Window) { + if (window.innerWidth === 1300) { + this.setState({ isBurgerMenuOpen: false }); + this.setState({ isProfileMenuOpen: false }); + } + } + private openBurgerMenu() { this.setState({ isBurgerMenuOpen: true }); } @@ -101,4 +118,12 @@ export default class Header extends React.Component { 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 index 8ae1e77b..f40fb3f6 100644 --- a/src/front/Stores/WindowStore.ts +++ b/src/front/Stores/WindowStore.ts @@ -21,8 +21,17 @@ export default class WindowStore { }; } + 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 = (() => { @@ -40,4 +49,8 @@ export default class WindowStore { previousYDirection = scrollYDirection; }; })(); + + private resizeHandler() { + this.event.emit("resize", window); + } } From e734665feaca759fce0722f002c73c91ea627811 Mon Sep 17 00:00:00 2001 From: Hugo Lextrait Date: Thu, 23 Mar 2023 14:38:49 +0100 Subject: [PATCH 3/3] add breackpoint variable --- src/front/Components/DesignSystem/Header/index.tsx | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/front/Components/DesignSystem/Header/index.tsx b/src/front/Components/DesignSystem/Header/index.tsx index 403882ad..b8c176be 100644 --- a/src/front/Components/DesignSystem/Header/index.tsx +++ b/src/front/Components/DesignSystem/Header/index.tsx @@ -28,6 +28,7 @@ type IState = { export default class Header extends React.Component { private onScrollYDirectionChange = () => { }; private onWindowResize = () => { }; + private headerBreakpoint = 1300; constructor(props: IProps) { super(props); @@ -87,7 +88,7 @@ export default class Header extends React.Component { private visibility(scrollYDirection: number) { let open: IState["open"] = EHeaderOpeningState.OPEN; - if (window.scrollY > 50 && scrollYDirection < 0 && Math.abs(scrollYDirection) > 8) { + if (window.scrollY > 50 && scrollYDirection < 0 && Math.abs(scrollYDirection) > 8 && window.innerWidth < this.headerBreakpoint) { open = EHeaderOpeningState.CLOSED; this.closeBurgerMenu(); this.closeNotificationMenu(); @@ -97,10 +98,8 @@ export default class Header extends React.Component { } private onResize(window: Window) { - if (window.innerWidth === 1300) { - this.setState({ isBurgerMenuOpen: false }); - this.setState({ isProfileMenuOpen: false }); - } + 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() {