18128: Adds Multi Panel View (MPV for short) component family.
[arvados.git] / src / components / multi-panel-view / multi-panel-view.tsx
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 import React, { ReactElement, ReactNode, useState } from 'react';
6 import { Button, Grid } from "@material-ui/core";
7 import { GridProps } from '@material-ui/core/Grid';
8 import { isArray } from 'lodash';
9 import { DefaultView } from 'components/default-view/default-view';
10 import { InfoIcon } from 'components/icon/icon';
11 import { ReactNodeArray } from 'prop-types';
12
13 interface MPVHideablePanelDataProps {
14     name: string;
15     visible: boolean;
16     children: ReactNode;
17 }
18
19 interface MPVHideablePanelActionProps {
20     doHidePanel: () => void;
21 }
22
23 type MPVPanelProps = MPVHideablePanelDataProps & MPVHideablePanelActionProps;
24
25 const MPVHideablePanel = ({doHidePanel, name, visible, ...props}: MPVPanelProps) =>
26     visible
27     ? <>
28         {React.cloneElement((props.children as ReactElement), { doHidePanel, panelName: name })}
29     </>
30     : null;
31
32 interface MPVPanelContentDataProps {
33     panelName?: string;
34     children: ReactElement;
35 }
36
37 interface MPVPanelContentActionProps {
38     doHidePanel?: () => void;
39 }
40
41 type MPVPanelContentProps = MPVPanelContentDataProps & MPVPanelContentActionProps & GridProps;
42
43 // Grid item compatible component for layout and MPV props passing
44 export const MPVPanelContent = ({doHidePanel, panelName, ...props}: MPVPanelContentProps) =>
45     <Grid item {...props}>
46         {React.cloneElement(props.children, { doHidePanel, panelName })}
47     </Grid>;
48
49 export interface MPVContainerDataProps {
50     panelNames?: string[];
51 }
52
53 type MPVContainerProps = MPVContainerDataProps & GridProps;
54
55 // Grid container compatible component that also handles panel toggling.
56 export const MPVContainer = ({children, panelNames, ...props}: MPVContainerProps) => {
57     if (children === undefined || children === null || children === {}) {
58         children = [];
59     } else if (!isArray(children)) {
60         children = [children];
61     }
62     const visibility = (children as ReactNodeArray).map(() => true);
63     const [panelVisibility, setPanelVisibility] = useState<boolean[]>(visibility);
64
65     let panels: JSX.Element[] = [];
66     let toggles: JSX.Element[] = [];
67
68     if (isArray(children)) {
69         for (let idx = 0; idx < children.length; idx++) {
70             const toggleFn = (idx: number) => () => {
71                 setPanelVisibility([
72                     ...panelVisibility.slice(0, idx),
73                     !panelVisibility[idx],
74                     ...panelVisibility.slice(idx+1)
75                 ])
76             };
77             const toggleLabel = panelVisibility[idx] ? 'Hide' : 'Show'
78             const panelName = panelNames === undefined
79                 ? `Panel ${idx+1}`
80                 : panelNames[idx] || `Panel ${idx+1}`;
81
82
83             toggles = [
84                 ...toggles,
85                 <Button onClick={toggleFn(idx)}>{toggleLabel} {panelName}</Button>
86             ];
87
88             const aPanel =
89                 <MPVHideablePanel visible={panelVisibility[idx]} name={panelName} doHidePanel={toggleFn(idx)}>
90                     {children[idx]}
91                 </MPVHideablePanel>;
92             panels = [...panels, aPanel];
93         };
94     };
95
96     return <Grid container {...props}>
97         { toggles }
98         { panelVisibility.includes(true)
99             ? panels
100             : <Grid container alignItems='center' justify='center'>
101                 <DefaultView messages={["All panels are hidden.", "Click on the buttons above to show them."]} icon={InfoIcon} />
102             </Grid> }
103     </Grid>;
104 };