Merge branch '14103-improve-dialogs'
[arvados-workbench2.git] / src / components / side-panel / side-panel.tsx
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 import * as React from 'react';
6 import { StyleRulesCallback, WithStyles, withStyles } from '@material-ui/core/styles';
7 import { ArvadosTheme } from '~/common/custom-theme';
8 import { List, ListItem, ListItemIcon, Collapse } from "@material-ui/core";
9 import { SidePanelRightArrowIcon, IconType } from '../icon/icon';
10 import * as classnames from "classnames";
11 import { ListItemTextIcon } from '../list-item-text-icon/list-item-text-icon';
12 import { Dispatch } from "redux";
13 import { RouteComponentProps, withRouter } from "react-router";
14
15 type CssRules = 'active' | 'row' | 'root' | 'list' | 'iconClose' | 'iconOpen' | 'toggableIconContainer' | 'toggableIcon';
16
17 const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
18     root: {
19         overflowY: 'auto',
20         minWidth: '240px',
21         whiteSpace: 'nowrap',
22         marginTop: '52px',
23         display: 'flex',
24         flexGrow: 1,
25     },
26     list: {
27         padding: '5px 0px 5px 14px',
28         minWidth: '240px',
29     },
30     row: {
31         display: 'flex',
32         alignItems: 'center',
33     },
34     toggableIconContainer: {
35         color: theme.palette.grey["700"],
36         height: '14px',
37         width: '14px'
38     },
39     toggableIcon: {
40         fontSize: '14px'
41     },
42     active: {
43         color: theme.palette.primary.main,
44     },
45     iconClose: {
46         transition: 'all 0.1s ease',
47     },
48     iconOpen: {
49         transition: 'all 0.1s ease',
50         transform: 'rotate(90deg)',
51     }
52 });
53
54 export interface SidePanelItem {
55     id: string;
56     name: string;
57     url: string;
58     icon: IconType;
59     open?: boolean;
60     margin?: boolean;
61     openAble?: boolean;
62     activeAction?: (dispatch: Dispatch, uuid?: string) => void;
63 }
64
65 interface SidePanelDataProps {
66     toggleOpen: (id: string) => void;
67     toggleActive: (id: string) => void;
68     sidePanelItems: SidePanelItem[];
69     onContextMenu: (event: React.MouseEvent<HTMLElement>, item: SidePanelItem) => void;
70 }
71
72 type SidePanelProps = RouteComponentProps<{}> & SidePanelDataProps & WithStyles<CssRules>;
73
74 export const SidePanel = withStyles(styles)(withRouter(
75     class extends React.Component<SidePanelProps> {
76         render() {
77             const { classes, toggleOpen, toggleActive, sidePanelItems, children } = this.props;
78             const { root, row, list, toggableIconContainer } = classes;
79
80             const path = this.props.location.pathname.split('/');
81             const activeUrl = path.length > 1 ? "/" + path[1] : "/";
82             return (
83                 <div className={root}>
84                     <List>
85                         {sidePanelItems.map(it => {
86                             const active = it.url === activeUrl;
87                             return <span key={it.name}>
88                                 <ListItem button className={list} onClick={() => toggleActive(it.id)}
89                                           onContextMenu={this.handleRowContextMenu(it)}>
90                                     <span className={row}>
91                                         {it.openAble ? (
92                                             <i onClick={() => toggleOpen(it.id)} className={toggableIconContainer}>
93                                                 <ListItemIcon
94                                                     className={this.getToggableIconClassNames(it.open, active)}>
95                                                     < SidePanelRightArrowIcon/>
96                                                 </ListItemIcon>
97                                             </i>
98                                         ) : null}
99                                         <ListItemTextIcon icon={it.icon} name={it.name} isActive={active}
100                                                           hasMargin={it.margin}/>
101                                     </span>
102                                 </ListItem>
103                                 {it.openAble ? (
104                                     <Collapse in={it.open} timeout="auto" unmountOnExit>
105                                         {children}
106                                     </Collapse>
107                                 ) : null}
108                             </span>;
109                         })}
110                     </List>
111                 </div>
112             );
113         }
114
115         getToggableIconClassNames = (isOpen?: boolean, isActive ?: boolean) => {
116             const { iconOpen, iconClose, active, toggableIcon } = this.props.classes;
117             return classnames(toggableIcon, {
118                 [iconOpen]: isOpen,
119                 [iconClose]: !isOpen,
120                 [active]: isActive
121             });
122         }
123
124         handleRowContextMenu = (item: SidePanelItem) =>
125             (event: React.MouseEvent<HTMLElement>) =>
126                 item.openAble ? this.props.onContextMenu(event, item) : null
127     }
128 ));