| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341 |
- import React, {
- FC, useCallback, useEffect, useRef, useState,
- } from 'react';
- import { scheduleToPutUserUISettings } from '~/client/services/user-ui-settings';
- import {
- useDrawerMode, useDrawerOpened,
- useSidebarCollapsed,
- useCurrentSidebarContents,
- useCurrentProductNavWidth,
- useSidebarResizeDisabled,
- } from '~/stores/ui';
- import DrawerToggler from './Navbar/DrawerToggler';
- import SidebarNav from './Sidebar/SidebarNav';
- import SidebarContents from './Sidebar/SidebarContents';
- import { NavigationResizeHexagon } from './Sidebar/NavigationResizeHexagon';
- import StickyStretchableScroller from './StickyStretchableScroller';
- const sidebarMinWidth = 240;
- const sidebarMinimizeWidth = 20;
- const GlobalNavigation = () => {
- const { data: currentContents } = useCurrentSidebarContents();
- const { data: isCollapsed, mutate: mutateSidebarCollapsed } = useSidebarCollapsed();
- const itemSelectedHandler = useCallback((selectedContents) => {
- let newValue = false;
- // already selected
- if (currentContents === selectedContents) {
- // toggle collapsed
- newValue = !isCollapsed;
- }
- mutateSidebarCollapsed(newValue, false);
- scheduleToPutUserUISettings({ isSidebarCollapsed: newValue });
- }, [currentContents, isCollapsed, mutateSidebarCollapsed]);
- return <SidebarNav onItemSelected={itemSelectedHandler} />;
- };
- // dummy skelton contents
- const GlobalNavigationSkelton = () => {
- return (
- <div className="grw-sidebar-nav">
- <div className="grw-sidebar-nav-primary-container">
- </div>
- <div className="grw-sidebar-nav-secondary-container">
- </div>
- </div>
- );
- };
- const SidebarContentsWrapper = () => {
- const scrollTargetSelector = '#grw-sidebar-contents-scroll-target';
- const calcViewHeight = useCallback(() => {
- const scrollTargetElem = document.querySelector('#grw-sidebar-contents-scroll-target');
- return scrollTargetElem != null
- ? window.innerHeight - scrollTargetElem?.getBoundingClientRect().top
- : window.innerHeight;
- }, []);
- return (
- <>
- <StickyStretchableScroller
- scrollTargetSelector={scrollTargetSelector}
- contentsElemSelector="#grw-sidebar-content-container"
- stickyElemSelector=".grw-sidebar"
- calcViewHeightFunc={calcViewHeight}
- />
- <div id="grw-sidebar-contents-scroll-target">
- <div id="grw-sidebar-content-container">
- <SidebarContents />
- </div>
- </div>
- <DrawerToggler iconClass="icon-arrow-left" />
- </>
- );
- };
- // dummy skelton contents
- const SidebarSkeltonContents = () => {
- return (
- <div>Skelton Contents!!!</div>
- );
- };
- type Props = {
- }
- const Sidebar: FC<Props> = (props: Props) => {
- const { data: isDrawerMode } = useDrawerMode();
- const { data: isDrawerOpened, mutate: mutateDrawerOpened } = useDrawerOpened();
- const { data: currentProductNavWidth, mutate: mutateProductNavWidth } = useCurrentProductNavWidth();
- const { data: isCollapsed, mutate: mutateSidebarCollapsed } = useSidebarCollapsed();
- const { data: isResizeDisabled, mutate: mutateSidebarResizeDisabled } = useSidebarResizeDisabled();
- const [isHover, setHover] = useState(false);
- const [isDragging, setDrag] = useState(false);
- const [isMounted, setMounted] = useState(false);
- const isResizableByDrag = !isResizeDisabled && !isDrawerMode && (!isCollapsed || isHover);
- /**
- * hack and override UIController.storeState
- *
- * Since UIController is an unstated container, setState() in storeState method should be awaited before writing to cache.
- */
- // hackUIController() {
- // const { navigationUIController } = this.props;
- // // see: @atlaskit/navigation-next/dist/esm/ui-controller/UIController.js
- // const orgStoreState = navigationUIController.storeState;
- // navigationUIController.storeState = async(state) => {
- // await navigationUIController.setState(state);
- // orgStoreState(state);
- // };
- // }
- const toggleDrawerMode = useCallback((bool) => {
- const isStateModified = isResizeDisabled !== bool;
- if (!isStateModified) {
- return;
- }
- // Drawer <-- Dock
- if (bool) {
- // // cache state
- // this.sidebarCollapsedCached = navigationUIController.state.isCollapsed;
- // this.sidebarWidthCached = navigationUIController.state.productNavWidth;
- // // clear transition temporary
- // if (this.sidebarCollapsedCached) {
- // this.addCssClassTemporary('grw-sidebar-supress-transitions-to-drawer');
- // }
- // disable resize
- mutateSidebarResizeDisabled(true, false);
- }
- // Drawer --> Dock
- else {
- // // clear transition temporary
- // if (this.sidebarCollapsedCached) {
- // this.addCssClassTemporary('grw-sidebar-supress-transitions-to-dock');
- // }
- // enable resize
- mutateSidebarResizeDisabled(false, false);
- // // restore width
- // if (this.sidebarWidthCached != null) {
- // navigationUIController.setState({ productNavWidth: this.sidebarWidthCached });
- // }
- }
- }, [isResizeDisabled, mutateSidebarResizeDisabled]);
- // addCssClassTemporary(className) {
- // // clear
- // this.sidebarElem.classList.add(className);
- // // restore after 300ms
- // setTimeout(() => {
- // this.sidebarElem.classList.remove(className);
- // }, 300);
- // }
- const backdropClickedHandler = useCallback(() => {
- mutateDrawerOpened(false, false);
- }, [mutateDrawerOpened]);
- useEffect(() => {
- // this.hackUIController();
- setMounted(true);
- }, []);
- useEffect(() => {
- toggleDrawerMode(isDrawerMode);
- }, [isDrawerMode, toggleDrawerMode]);
- const resizableContainer = useRef<HTMLDivElement>(null);
- const setContentWidth = useCallback((newWidth) => {
- if (resizableContainer.current == null) {
- return;
- }
- resizableContainer.current.style.width = `${newWidth}px`;
- }, []);
- const hoverOnResizableContainerHandler = useCallback(() => {
- if (!isCollapsed || isDrawerMode || isDragging) {
- return;
- }
- setHover(true);
- setContentWidth(currentProductNavWidth);
- }, [isCollapsed, isDrawerMode, isDragging, setContentWidth, currentProductNavWidth]);
- const hoverOutHandler = useCallback(() => {
- if (!isCollapsed || isDrawerMode || isDragging) {
- return;
- }
- setHover(false);
- setContentWidth(sidebarMinimizeWidth);
- }, [isCollapsed, isDragging, isDrawerMode, setContentWidth]);
- const toggleNavigationBtnClickHandler = useCallback(() => {
- const newValue = !isCollapsed;
- mutateSidebarCollapsed(newValue, false);
- scheduleToPutUserUISettings({ isSidebarCollapsed: newValue });
- }, [isCollapsed, mutateSidebarCollapsed]);
- useEffect(() => {
- if (isCollapsed) {
- setContentWidth(sidebarMinimizeWidth);
- }
- else {
- setContentWidth(currentProductNavWidth);
- }
- }, [currentProductNavWidth, isCollapsed, setContentWidth]);
- const draggableAreaMoveHandler = useCallback((event: MouseEvent) => {
- event.preventDefault();
- const newWidth = event.pageX - 60;
- if (resizableContainer.current != null) {
- setContentWidth(newWidth);
- resizableContainer.current.classList.add('dragging');
- }
- }, [setContentWidth]);
- const dragableAreaMouseUpHandler = useCallback(() => {
- if (resizableContainer.current == null) {
- return;
- }
- setDrag(false);
- if (resizableContainer.current.clientWidth < sidebarMinWidth) {
- // force collapsed
- mutateSidebarCollapsed(true);
- mutateProductNavWidth(sidebarMinWidth, false);
- scheduleToPutUserUISettings({ isSidebarCollapsed: true, currentProductNavWidth: sidebarMinWidth });
- }
- else {
- const newWidth = resizableContainer.current.clientWidth;
- mutateSidebarCollapsed(false);
- mutateProductNavWidth(newWidth, false);
- scheduleToPutUserUISettings({ isSidebarCollapsed: false, currentProductNavWidth: newWidth });
- }
- resizableContainer.current.classList.remove('dragging');
- }, [mutateProductNavWidth, mutateSidebarCollapsed]);
- const dragableAreaMouseDownHandler = useCallback((event: React.MouseEvent) => {
- if (!isResizableByDrag) {
- return;
- }
- event.preventDefault();
- setDrag(true);
- const removeEventListeners = () => {
- document.removeEventListener('mousemove', draggableAreaMoveHandler);
- document.removeEventListener('mouseup', dragableAreaMouseUpHandler);
- document.removeEventListener('mouseup', removeEventListeners);
- };
- document.addEventListener('mousemove', draggableAreaMoveHandler);
- document.addEventListener('mouseup', dragableAreaMouseUpHandler);
- document.addEventListener('mouseup', removeEventListeners);
- }, [dragableAreaMouseUpHandler, draggableAreaMoveHandler, isResizableByDrag]);
- return (
- <>
- <div className={`grw-sidebar d-print-none ${isDrawerMode ? 'grw-sidebar-drawer' : ''} ${isDrawerOpened ? 'open' : ''}`}>
- <div className="data-layout-container">
- <div className="navigation" onMouseLeave={hoverOutHandler}>
- <div className="grw-navigation-wrap">
- <div className="grw-global-navigation">
- { isMounted ? <GlobalNavigation></GlobalNavigation> : <GlobalNavigationSkelton></GlobalNavigationSkelton> }
- </div>
- <div
- ref={resizableContainer}
- className="grw-contextual-navigation"
- onMouseEnter={hoverOnResizableContainerHandler}
- style={{ width: isCollapsed ? sidebarMinimizeWidth : currentProductNavWidth }}
- >
- <div className="grw-contextual-navigation-child">
- <div role="group" className={`grw-contextual-navigation-sub ${!isHover && isCollapsed ? 'collapsed' : ''}`}>
- { isMounted ? <SidebarContentsWrapper></SidebarContentsWrapper> : <SidebarSkeltonContents></SidebarSkeltonContents> }
- </div>
- </div>
- </div>
- </div>
- <div className="grw-navigation-draggable">
- { isResizableByDrag && (
- <div
- className="grw-navigation-draggable-hitarea"
- onMouseDown={dragableAreaMouseDownHandler}
- >
- <div className="grw-navigation-draggable-hitarea-child"></div>
- </div>
- ) }
- <button
- className={`grw-navigation-resize-button ${!isDrawerMode ? 'resizable' : ''} ${isCollapsed ? 'collapsed' : ''} `}
- type="button"
- aria-expanded="true"
- aria-label="Toggle navigation"
- disabled={isDrawerMode}
- onClick={toggleNavigationBtnClickHandler}
- >
- <span className="hexagon-container" role="presentation">
- <NavigationResizeHexagon />
- </span>
- <span className="hitarea" role="presentation"></span>
- </button>
- </div>
- </div>
- </div>
- </div>
- { isDrawerOpened && (
- <div className="grw-sidebar-backdrop modal-backdrop show" onClick={backdropClickedHandler}></div>
- ) }
- </>
- );
- };
- export default Sidebar;
|