StandardAppLayout.tsx 4.67 KB
Newer Older
1
import React, { ReactNode, useEffect } from 'react';
Andrey Azov's avatar
Andrey Azov committed
2
3
4
import classNames from 'classnames';
import noop from 'lodash/noop';

5
6
7
import { BreakpointWidth } from 'src/global/globalConfig';
import usePrevious from 'src/shared/hooks/usePrevious';

Andrey Azov's avatar
Andrey Azov committed
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import { ReactComponent as Chevron } from 'static/img/shared/chevron-right.svg';
import { ReactComponent as CloseIcon } from 'static/img/shared/close.svg';

import styles from './StandardAppLayout.scss';

enum SidebarModeToggleAction {
  OPEN = 'open',
  CLOSE = 'close'
}

type SidebarModeToggleProps = {
  onClick: () => void;
  showAction: SidebarModeToggleAction;
};

type StandardAppLayoutProps = {
  mainContent: ReactNode;
  sidebarContent: ReactNode;
  sidebarToolstripContent?: ReactNode;
27
  sidebarNavigation: ReactNode;
Andrey Azov's avatar
Andrey Azov committed
28
29
30
31
32
33
  topbarContent: ReactNode;
  isSidebarOpen: boolean;
  onSidebarToggle: () => void;
  isDrawerOpen: boolean;
  drawerContent?: ReactNode;
  onDrawerClose: () => void;
34
  viewportWidth: BreakpointWidth;
Andrey Azov's avatar
Andrey Azov committed
35
36
37
};

const StandardAppLayout = (props: StandardAppLayoutProps) => {
38
39
40
41
42
43
44
45
46
  // TODO: is there any way to run this smarter?
  // Ideally, it should run only once per app life cycle to check whether user is on small screen and close the sidebar if they are
  useEffect(() => {
    if (props.viewportWidth < BreakpointWidth.DESKTOP && props.isSidebarOpen) {
      props.onSidebarToggle();
    }
  }, []);

  const mainClassNames = classNames(
Andrey Azov's avatar
Andrey Azov committed
47
48
49
50
51
    styles.main,
    { [styles.mainDefault]: props.isSidebarOpen },
    { [styles.mainFullWidth]: !props.isSidebarOpen }
  );

52
53
54
55
56
57
58
  const shouldShowSidebarNavigation =
    props.viewportWidth > BreakpointWidth.LAPTOP || props.isSidebarOpen;

  const topBarClassnames = classNames(
    styles.topBar,
    { [styles.topBar_withSidebarNavigation]: shouldShowSidebarNavigation },
    { [styles.topBar_withoutSidebarNavigation]: !shouldShowSidebarNavigation }
Andrey Azov's avatar
Andrey Azov committed
59
60
  );

61
62
  const sidebarWrapperClassnames = useSidebarWrapperClassNames(props);

Andrey Azov's avatar
Andrey Azov committed
63
64
  return (
    <div className={styles.standardAppLayout}>
65
      <div className={topBarClassnames}>
Andrey Azov's avatar
Andrey Azov committed
66
        {props.topbarContent}
67
        {shouldShowSidebarNavigation && props.sidebarNavigation}
Andrey Azov's avatar
Andrey Azov committed
68
69
      </div>
      <div className={styles.mainWrapper}>
70
        <div className={mainClassNames}>{props.mainContent}</div>
Andrey Azov's avatar
Andrey Azov committed
71
72
73
74
75
        <div className={sidebarWrapperClassnames}>
          {props.isDrawerOpen && <DrawerWindow onClick={props.onDrawerClose} />}
          <div className={styles.sideBarToolstrip}>
            <SidebarModeToggle
              onClick={
76
77
78
                props.isDrawerOpen
                  ? () => props.onDrawerClose()
                  : () => props.onSidebarToggle()
Andrey Azov's avatar
Andrey Azov committed
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
              }
              showAction={
                props.isSidebarOpen
                  ? SidebarModeToggleAction.CLOSE
                  : SidebarModeToggleAction.OPEN
              }
            />
            <div className={styles.sideBarToolstripContent}>
              {props.sidebarToolstripContent}
            </div>
          </div>
          <div className={styles.sideBar}>{props.sidebarContent}</div>
          <div className={styles.drawer}>
            <CloseIcon
              className={styles.drawerClose}
              onClick={props.onDrawerClose}
            />
            {props.drawerContent || null}
          </div>
        </div>
      </div>
    </div>
  );
};

StandardAppLayout.defaultProps = {
  isDrawerOpen: false,
  onDrawerClose: noop
};

const SidebarModeToggle = (props: SidebarModeToggleProps) => {
  const chevronClasses = classNames(styles.sidebarModeToggleChevron, {
    [styles.sidebarModeToggleChevronOpen]:
      props.showAction === SidebarModeToggleAction.OPEN
  });

  return (
    <div className={styles.sidebarModeToggle}>
      <Chevron className={chevronClasses} onClick={props.onClick} />
    </div>
  );
};

// left-most transparent part of the drawer allowing the user to see what element is behind the drawer;
// when clicked, will close the drawer
const DrawerWindow = (props: { onClick: () => void }) => {
  return <div className={styles.drawerWindow} onClick={props.onClick} />;
};

128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
const useSidebarWrapperClassNames = (props: StandardAppLayoutProps) => {
  const previousSidebarOpen = usePrevious(props.isSidebarOpen);
  // do not use transition for opening and closing of the sidebar
  const isInstantaneous =
    !props.isSidebarOpen || // <-- sidebar about to close
    (props.isSidebarOpen && !previousSidebarOpen); // <-- sidebar about to open

  return classNames(
    styles.sideBarWrapper,
    { [styles.sideBarWrapperOpen]: props.isSidebarOpen },
    { [styles.sideBarWrapperClosed]: !props.isSidebarOpen },
    {
      [styles.sideBarWrapperDrawerOpen]: props.isDrawerOpen ?? false
    },
    { [styles.instantaneous]: isInstantaneous }
  );
};

Andrey Azov's avatar
Andrey Azov committed
146
export default StandardAppLayout;